Skip to main content

eth_state_diff/
fifo_queue.rs

1//! Delta encoding for fixed-size SSZ FIFO queues.
2//!
3//! A queue transition is represented as the number of items consumed from the
4//! front of the queue together with the raw SSZ bytes of newly appended items.
5//!
6//! This encoding is suitable for append-only FIFO structures such as the
7//! Electra pending operation queues.
8
9use crate::types::{ArchivedFifoQueueDiff, FifoQueueDiff};
10
11/// Finds the first occurrence of an SSZ-encoded queue item within a byte slice.
12///
13/// Returns the byte offset of the first matching item, or `None` if no match is
14/// found.
15fn find_chunk(haystack: &[u8], needle: &[u8]) -> Option<usize> {
16    haystack
17        .windows(needle.len())
18        .position(|window| window == needle)
19}
20
21/// Computes the delta between two serialized FIFO queues.
22///
23/// The queues are expected to contain fixed-size SSZ items.
24///
25/// The resulting delta records:
26///
27/// - the number of items consumed from the front of the base queue; and
28/// - the SSZ bytes of items appended to the end of the queue.
29///
30/// If no overlap between the queues can be identified, the encoder assumes the
31/// base queue was entirely consumed.
32///
33/// # Algorithm
34///
35/// The encoder identifies the overlap by locating the first item in the target
36/// queue within the base queue. This assumes queue items are sufficiently
37/// unique that the first match represents the correct continuation of the FIFO
38/// sequence.
39pub fn diff_fifo_queue(base_ssz: &[u8], target_ssz: &[u8], item_ssz_size: usize) -> FifoQueueDiff {
40    if target_ssz.is_empty() {
41        return FifoQueueDiff {
42            consumed_count: base_ssz.len() as u32 / item_ssz_size as u32,
43            appended_items: Vec::new(),
44        };
45    }
46
47    // If target is larger than base, no items were consumed, it's pure append
48    if target_ssz.len() >= base_ssz.len() {
49        let appended = if base_ssz.is_empty() {
50            target_ssz.to_vec()
51        } else {
52            target_ssz[base_ssz.len()..].to_vec()
53        };
54        return FifoQueueDiff {
55            consumed_count: 0,
56            appended_items: appended,
57        };
58    }
59
60    // Target shrank. We need to find how many items were consumed.
61    // Take the SSZ bytes of the first item in the target queue
62    let target_head = &target_ssz[..item_ssz_size];
63
64    match find_chunk(base_ssz, target_head) {
65        Some(byte_offset) => {
66            let consumed_count = (byte_offset / item_ssz_size) as u32;
67            let remaining_base_items = (base_ssz.len() - byte_offset) as u32;
68            let target_item_count = (target_ssz.len() / item_ssz_size) as u32;
69
70            let new_item_count = target_item_count.saturating_sub(remaining_base_items);
71            let new_items_start = target_ssz.len() - (new_item_count as usize * item_ssz_size);
72
73            FifoQueueDiff {
74                consumed_count,
75                appended_items: target_ssz[new_items_start..].to_vec(),
76            }
77        }
78        None => FifoQueueDiff {
79            consumed_count: base_ssz.len() as u32 / item_ssz_size as u32,
80            appended_items: target_ssz.to_vec(),
81        },
82    }
83}
84
85/// Applies a FIFO queue delta in place.
86///
87/// The destination queue is updated by:
88///
89/// 1. Removing `consumed_count` items from the front.
90/// 2. Appending the recorded SSZ items to the end.
91///
92/// If `consumed_count` exceeds the current queue length, the destination queue
93/// is cleared before appending.
94///
95/// # Complexity
96///
97/// O(queue size + appended bytes)
98pub fn apply_fifo_queue(base: &mut Vec<u8>, delta: &ArchivedFifoQueueDiff, item_ssz_size: usize) {
99    let bytes_to_drain = delta.consumed_count.to_native() as usize * item_ssz_size;
100    if bytes_to_drain > base.len() {
101        base.clear();
102    } else {
103        base.drain(..bytes_to_drain);
104    }
105
106    if !delta.appended_items.is_empty() {
107        base.extend_from_slice(delta.appended_items.as_slice());
108    }
109}