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}