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
//! [EIP-7685]: General purpose execution layer requests
//!
//! [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685

use alloc::vec::Vec;
use alloy_primitives::{b256, Bytes, B256};
use derive_more::{Deref, DerefMut, From, IntoIterator};

/// The empty requests hash.
///
/// This is equivalent to `sha256("")`
pub const EMPTY_REQUESTS_HASH: B256 =
    b256!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");

/// A container of EIP-7685 requests.
///
/// The container only holds the `requests` as defined by their respective EIPs. The first byte of
/// each element is the `request_type` and the remaining bytes are the `request_data`.
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Deref, DerefMut, From, IntoIterator)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Requests(Vec<Bytes>);

impl Requests {
    /// Construct a new [`Requests`] container with the given capacity.
    pub fn with_capacity(capacity: usize) -> Self {
        Self(Vec::with_capacity(capacity))
    }

    /// Construct a new [`Requests`] container.
    ///
    /// This function assumes that the request type byte is already included as the
    /// first byte in the provided `Bytes` blob.
    pub const fn new(requests: Vec<Bytes>) -> Self {
        Self(requests)
    }

    /// Add a new request into the container.
    pub fn push_request(&mut self, request: Bytes) {
        // Omit empty requests.
        if request.len() == 1 {
            return;
        }
        self.0.push(request);
    }

    /// Adds a new request with the given request type into the container.
    pub fn push_request_with_type(
        &mut self,
        request_type: u8,
        request: impl IntoIterator<Item = u8>,
    ) {
        let mut request = request.into_iter().peekable();
        // Omit empty requests.
        if request.peek().is_none() {
            return;
        }
        self.0.push(core::iter::once(request_type).chain(request).collect());
    }

    /// Consumes [`Requests`] and returns the inner raw opaque requests.
    ///
    /// # Note
    ///
    /// These requests include the `request_type` as the first byte in each
    /// `Bytes` element, followed by the `requests_data`.
    pub fn take(self) -> Vec<Bytes> {
        self.0
    }

    /// Get an iterator over the requests.
    pub fn iter(&self) -> core::slice::Iter<'_, Bytes> {
        self.0.iter()
    }

    /// Calculate the requests hash as defined in EIP-7685 for the requests.
    ///
    /// The requests hash is defined as
    ///
    /// ```text
    /// sha256(sha256(requests_0) ++ sha256(requests_1) ++ ...)
    /// ```
    ///
    /// Each request in the container is expected to already have the `request_type` prepended
    /// to its corresponding `requests_data`. This function directly calculates the hash based
    /// on the combined `request_type` and `requests_data`.
    ///
    /// Empty requests are omitted from the hash calculation.
    /// Requests are sorted by their `request_type` before hashing, see also [Ordering](https://eips.ethereum.org/EIPS/eip-7685#ordering)
    #[cfg(feature = "sha2")]
    pub fn requests_hash(&self) -> B256 {
        use sha2::{Digest, Sha256};
        let mut hash = Sha256::new();

        let mut requests: Vec<_> = self.0.iter().filter(|req| !req.is_empty()).collect();
        requests.sort_unstable_by_key(|req| {
            // SAFETY: only includes non-empty requests
            req[0]
        });

        for req in requests {
            let mut req_hash = Sha256::new();
            req_hash.update(req);
            hash.update(req_hash.finalize());
        }
        B256::new(hash.finalize().into())
    }

    /// Extend this container with requests from another container.
    pub fn extend(&mut self, other: Self) {
        self.0.extend(other.take());
    }
}

/// A list of requests or a precomputed requests hash.
///
/// For testing purposes, the `Hash` variant stores a precomputed requests hash. This can be useful
/// when the exact contents of the requests are unnecessary, and only a consistent hash value is
/// needed to simulate the presence of requests without holding actual data.
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RequestsOrHash {
    /// Stores a list of requests, allowing for dynamic requests hash calculation.
    Requests(Requests),
    /// Stores a precomputed requests hash, used primarily for testing or mocking because the
    /// header only contains the hash.
    Hash(B256),
}

impl RequestsOrHash {
    /// Returns the requests hash for the enum instance.
    ///
    /// - If the instance contains a list of requests, this function calculates the hash using
    ///   `requests_hash` of the [`Requests`] struct.
    /// - If it contains a precomputed hash, it returns that hash directly.
    #[cfg(feature = "sha2")]
    pub fn requests_hash(&self) -> B256 {
        match self {
            Self::Requests(requests) => requests.requests_hash(),
            Self::Hash(precomputed_hash) => *precomputed_hash,
        }
    }

    /// Returns an instance with the [`EMPTY_REQUESTS_HASH`].
    pub const fn empty() -> Self {
        Self::Hash(EMPTY_REQUESTS_HASH)
    }

    /// Returns the requests, if any.
    pub const fn requests(&self) -> Option<&Requests> {
        match self {
            Self::Requests(requests) => Some(requests),
            Self::Hash(_) => None,
        }
    }
}

impl Default for RequestsOrHash {
    fn default() -> Self {
        Self::Requests(Requests::default())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_extend() {
        // Test extending a Requests container with another Requests container
        let mut reqs1 = Requests::new(vec![Bytes::from(vec![0x01, 0x02])]);
        let reqs2 =
            Requests::new(vec![Bytes::from(vec![0x03, 0x04]), Bytes::from(vec![0x05, 0x06])]);

        // Extend reqs1 with reqs2
        reqs1.extend(reqs2);

        // Ensure the requests are correctly combined
        assert_eq!(reqs1.0.len(), 3);
        assert_eq!(
            reqs1.0,
            vec![
                Bytes::from(vec![0x01, 0x02]),
                Bytes::from(vec![0x03, 0x04]),
                Bytes::from(vec![0x05, 0x06])
            ]
        );
    }

    #[test]
    #[cfg(feature = "sha2")]
    fn test_consistent_requests_hash() {
        // We test that the empty requests hash is consistent with the EIP-7685 definition.
        assert_eq!(Requests::default().requests_hash(), EMPTY_REQUESTS_HASH);

        // Test to hash a non-empty vector of requests.
        assert_eq!(
            Requests(vec![
                Bytes::from(vec![0x00, 0x0a, 0x0b, 0x0c]),
                Bytes::from(vec![0x01, 0x0d, 0x0e, 0x0f])
            ])
            .requests_hash(),
            b256!("be3a57667b9bb9e0275019c0faf0f415fdc8385a408fd03e13a5c50615e3530c"),
        );
    }
}