Skip to main content

eth_state_diff/
attestations.rs

1//! Delta encoding for Phase 0 pending attestation lists.
2//!
3//! Phase 0 maintains two attestation lists with different update semantics:
4//!
5//! - `current_epoch_attestations` grows by appending new attestations during an
6//!   epoch.
7//! - `previous_epoch_attestations` is replaced when the epoch transitions.
8//!
9//! This module provides a single diff function that selects the most compact
10//! supported representation for both cases. If the target list starts with the
11//! exact byte sequence of the base list, only the appended bytes are stored.
12//! Otherwise, the complete target list is stored as a full replacement.
13//!
14//! Both representations use [`AttestationsDiff`] and can be applied with
15//! [`apply_attestations`] without deserializing individual attestations.
16
17use crate::types::{ArchivedAttestationsDiff, AttestationsDiff};
18
19/// Computes a compact delta between two serialized SSZ attestation lists.
20///
21/// The function automatically selects between the supported delta
22/// representations:
23///
24/// - If the lists are identical, [`AttestationsDiff::Unchanged`] is returned.
25/// - If the target list starts with the exact byte sequence of the base list,
26///   only the trailing bytes are stored in [`AttestationsDiff::Append`].
27/// - Otherwise, the complete target list is stored in
28///   [`AttestationsDiff::FullReplacement`].
29///
30/// This covers both append-only updates, such as growth of
31/// `current_epoch_attestations`, and epoch-boundary replacement of
32/// `previous_epoch_attestations`.
33///
34/// An empty base list is naturally represented as an append of the complete
35/// target list.
36///
37/// # Arguments
38///
39/// * `base_ssz` - Serialized SSZ representation of the base attestation list.
40/// * `target_ssz` - Serialized SSZ representation of the target attestation list.
41///
42/// # Returns
43///
44/// A compact [`AttestationsDiff`] representing the transition from `base_ssz`
45/// to `target_ssz`.
46///
47/// # Complexity
48///
49/// The prefix comparison takes O(min(n, m)) time, where *n* and *m* are the
50/// lengths of `base_ssz` and `target_ssz`, respectively. Additional space is
51/// proportional to the selected delta payload.
52///
53/// # Example
54///
55/// ```
56/// # use eth_state_diff::attestations::diff_attestations;
57/// # use eth_state_diff::types::AttestationsDiff;
58///
59/// let base = b"AAAA";
60///
61/// // Append case.
62/// let target = b"AAAABBBB";
63/// assert_eq!(
64///     diff_attestations(base, target),
65///     AttestationsDiff::Append(b"BBBB".to_vec())
66/// );
67///
68/// // Replacement case.
69/// let target = b"CCCC";
70/// assert_eq!(
71///     diff_attestations(base, target),
72///     AttestationsDiff::FullReplacement(b"CCCC".to_vec())
73/// );
74/// ```
75pub fn diff_attestations(base_ssz: &[u8], target_ssz: &[u8]) -> AttestationsDiff {
76    if base_ssz == target_ssz {
77        return AttestationsDiff::Unchanged;
78    }
79
80    if target_ssz.starts_with(base_ssz) {
81        let Some(appended) = target_ssz.get(base_ssz.len()..) else {
82            return AttestationsDiff::FullReplacement(target_ssz.to_vec());
83        };
84
85        AttestationsDiff::Append(appended.to_vec())
86    } else {
87        AttestationsDiff::FullReplacement(target_ssz.to_vec())
88    }
89}
90
91/// Applies an attestation delta to a serialized SSZ list in place.
92///
93/// [`AttestationsDiff::Unchanged`] leaves the base buffer untouched.
94///
95/// [`AttestationsDiff::Append`] appends the serialized bytes stored in the
96/// delta to the existing buffer.
97///
98/// [`AttestationsDiff::FullReplacement`] clears the existing buffer and
99/// replaces it with the serialized target bytes.
100///
101/// # Complexity
102///
103/// - [`AttestationsDiff::Unchanged`][]: O(1).
104/// - [`AttestationsDiff::Append`]: O(k), where *k* is the number of appended
105///   bytes.
106/// - [`AttestationsDiff::FullReplacement`]: O(n), where *n* is the size of the
107///   replacement list.
108///
109/// # Example
110///
111/// ```
112/// # use eth_state_diff::attestations::{apply_attestations, diff_attestations};
113/// # use eth_state_diff::types::AttestationsDiff;
114///
115/// let mut base = b"AAAA".to_vec();
116/// let delta = diff_attestations(&base, b"AAAABBBB");
117///
118/// match delta {
119///     AttestationsDiff::Unchanged => {}
120///     AttestationsDiff::Append(bytes) => base.extend_from_slice(&bytes),
121///     AttestationsDiff::FullReplacement(bytes) => {
122///         base.clear();
123///         base.extend_from_slice(&bytes);
124///     }
125/// }
126///
127/// assert_eq!(base, b"AAAABBBB");
128/// ```
129pub fn apply_attestations(base: &mut Vec<u8>, delta: &ArchivedAttestationsDiff) {
130    match delta {
131        ArchivedAttestationsDiff::Unchanged => {}
132        ArchivedAttestationsDiff::Append(bytes) => {
133            base.extend_from_slice(bytes.as_slice());
134        }
135        ArchivedAttestationsDiff::FullReplacement(bytes) => {
136            base.clear();
137            base.extend_from_slice(bytes.as_slice());
138        }
139    }
140}