alloy_network_primitives/
block.rs

1use alloy_primitives::B256;
2
3use crate::TransactionResponse;
4use alloc::{vec, vec::Vec};
5use alloy_consensus::error::ValueError;
6use alloy_eips::Encodable2718;
7use core::slice;
8
9/// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`,
10/// or if used by `eth_getUncle*`
11#[derive(Clone, Debug, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "serde", serde(untagged))]
14pub enum BlockTransactions<T> {
15    /// Full transactions
16    Full(Vec<T>),
17    /// Only hashes
18    Hashes(Vec<B256>),
19    /// Special case for uncle response.
20    Uncle,
21}
22
23impl<T> Default for BlockTransactions<T> {
24    fn default() -> Self {
25        Self::Hashes(Vec::default())
26    }
27}
28
29impl<T> BlockTransactions<T> {
30    /// Check if the enum variant is used for hashes.
31    #[inline]
32    pub const fn is_hashes(&self) -> bool {
33        matches!(self, Self::Hashes(_))
34    }
35
36    /// Fallibly cast to a slice of hashes.
37    pub fn as_hashes(&self) -> Option<&[B256]> {
38        match self {
39            Self::Hashes(hashes) => Some(hashes),
40            _ => None,
41        }
42    }
43
44    /// Returns the first transaction if the transactions are full.
45    pub fn first_transaction(&self) -> Option<&T> {
46        self.as_transactions().and_then(|txs| txs.first())
47    }
48
49    /// Returns true if the enum variant is used for full transactions.
50    #[inline]
51    pub const fn is_full(&self) -> bool {
52        matches!(self, Self::Full(_))
53    }
54
55    /// Converts the transaction type by applying a function to each transaction.
56    ///
57    /// Returns the block with the new transaction type.
58    pub fn map<U>(self, f: impl FnMut(T) -> U) -> BlockTransactions<U> {
59        match self {
60            Self::Full(txs) => BlockTransactions::Full(txs.into_iter().map(f).collect()),
61            Self::Hashes(hashes) => BlockTransactions::Hashes(hashes),
62            Self::Uncle => BlockTransactions::Uncle,
63        }
64    }
65
66    /// Converts the transaction type by applying a fallible function to each transaction.
67    ///
68    /// Returns the block with the new transaction type if all transactions were successfully.
69    pub fn try_map<U, E>(
70        self,
71        f: impl FnMut(T) -> Result<U, E>,
72    ) -> Result<BlockTransactions<U>, E> {
73        match self {
74            Self::Full(txs) => {
75                Ok(BlockTransactions::Full(txs.into_iter().map(f).collect::<Result<_, _>>()?))
76            }
77            Self::Hashes(hashes) => Ok(BlockTransactions::Hashes(hashes)),
78            Self::Uncle => Ok(BlockTransactions::Uncle),
79        }
80    }
81
82    /// Fallibly cast to a slice of transactions.
83    ///
84    /// Returns `None` if the enum variant is not `Full`.
85    pub fn as_transactions(&self) -> Option<&[T]> {
86        match self {
87            Self::Full(txs) => Some(txs),
88            _ => None,
89        }
90    }
91
92    /// Calculate the transaction root for the full transactions.
93    ///
94    /// Returns `None` if this is not the [`BlockTransactions::Full`] variant
95    pub fn calculate_transactions_root(&self) -> Option<B256>
96    where
97        T: Encodable2718,
98    {
99        self.as_transactions().map(alloy_consensus::proofs::calculate_transaction_root)
100    }
101
102    /// Returns true if the enum variant is used for an uncle response.
103    #[inline]
104    pub const fn is_uncle(&self) -> bool {
105        matches!(self, Self::Uncle)
106    }
107
108    /// Returns an iterator over the transactions (if any). This will be empty
109    /// if the block is an uncle or if the transaction list contains only
110    /// hashes.
111    #[doc(alias = "transactions")]
112    pub fn txns(&self) -> impl Iterator<Item = &T> {
113        self.as_transactions().map(|txs| txs.iter()).unwrap_or_else(|| [].iter())
114    }
115
116    /// Returns an iterator over the transactions (if any). This will be empty if the block is not
117    /// full.
118    pub fn into_transactions(self) -> vec::IntoIter<T> {
119        match self {
120            Self::Full(txs) => txs.into_iter(),
121            _ => vec::IntoIter::default(),
122        }
123    }
124
125    /// Consumes the type and returns the transactions as a vector.
126    ///
127    /// Note: if this is an uncle or hashes, this will return an empty vector.
128    pub fn into_transactions_vec(self) -> Vec<T> {
129        match self {
130            Self::Full(txs) => txs,
131            _ => vec![],
132        }
133    }
134
135    /// Attempts to unwrap the [`Self::Full`] variant.
136    ///
137    /// Returns an error if the type is different variant.
138    pub fn try_into_transactions(self) -> Result<Vec<T>, ValueError<Self>> {
139        match self {
140            Self::Full(txs) => Ok(txs),
141            txs @ Self::Hashes(_) => Err(ValueError::new_static(txs, "Unexpected hashes variant")),
142            txs @ Self::Uncle => Err(ValueError::new_static(txs, "Unexpected uncle variant")),
143        }
144    }
145
146    /// Returns an instance of BlockTransactions with the Uncle special case.
147    #[inline]
148    pub const fn uncle() -> Self {
149        Self::Uncle
150    }
151
152    /// Returns the number of transactions.
153    #[inline]
154    pub const fn len(&self) -> usize {
155        match self {
156            Self::Hashes(h) => h.len(),
157            Self::Full(f) => f.len(),
158            Self::Uncle => 0,
159        }
160    }
161
162    /// Whether the block has no transactions.
163    #[inline]
164    pub const fn is_empty(&self) -> bool {
165        self.len() == 0
166    }
167}
168
169impl<T: TransactionResponse> BlockTransactions<T> {
170    /// Creates a new [`BlockTransactions::Hashes`] variant from the given iterator of transactions.
171    pub fn new_hashes(txs: impl IntoIterator<Item = impl AsRef<T>>) -> Self {
172        Self::Hashes(txs.into_iter().map(|tx| tx.as_ref().tx_hash()).collect())
173    }
174
175    /// Converts `self` into `Hashes`.
176    #[inline]
177    pub fn convert_to_hashes(&mut self) {
178        if !self.is_hashes() {
179            *self = Self::Hashes(self.hashes().collect());
180        }
181    }
182
183    /// Converts `self` into `Hashes` if the given `condition` is true.
184    #[inline]
185    pub fn convert_to_hashes_if(&mut self, condition: bool) {
186        if !condition {
187            return;
188        }
189        self.convert_to_hashes();
190    }
191
192    /// Converts `self` into `Hashes`.
193    #[inline]
194    pub fn into_hashes(mut self) -> Self {
195        self.convert_to_hashes();
196        self
197    }
198
199    /// Converts `self` into `Hashes` if the given `condition` is true.
200    #[inline]
201    pub fn into_hashes_if(self, condition: bool) -> Self {
202        if !condition {
203            return self;
204        }
205        self.into_hashes()
206    }
207
208    /// Returns an iterator over references to the transaction hashes.
209    #[inline]
210    pub fn hashes(&self) -> BlockTransactionHashes<'_, T> {
211        BlockTransactionHashes::new(self)
212    }
213
214    /// Consumes the type and returns the hashes as a vector.
215    ///
216    /// Note: if this is an uncle this will return an empty vector.
217    pub fn into_hashes_vec(self) -> Vec<B256> {
218        match self {
219            Self::Hashes(hashes) => hashes,
220            this => this.hashes().collect(),
221        }
222    }
223}
224
225impl<T> From<Vec<B256>> for BlockTransactions<T> {
226    fn from(hashes: Vec<B256>) -> Self {
227        Self::Hashes(hashes)
228    }
229}
230
231impl<T: TransactionResponse> From<Vec<T>> for BlockTransactions<T> {
232    fn from(transactions: Vec<T>) -> Self {
233        Self::Full(transactions)
234    }
235}
236
237/// An iterator over the transaction hashes of a block.
238///
239/// See [`BlockTransactions::hashes`].
240#[derive(Clone, Debug)]
241pub struct BlockTransactionHashes<'a, T>(BlockTransactionHashesInner<'a, T>);
242
243#[derive(Clone, Debug)]
244enum BlockTransactionHashesInner<'a, T> {
245    Hashes(slice::Iter<'a, B256>),
246    Full(slice::Iter<'a, T>),
247    Uncle,
248}
249
250impl<'a, T> BlockTransactionHashes<'a, T> {
251    #[inline]
252    fn new(txs: &'a BlockTransactions<T>) -> Self {
253        Self(match txs {
254            BlockTransactions::Hashes(txs) => BlockTransactionHashesInner::Hashes(txs.iter()),
255            BlockTransactions::Full(txs) => BlockTransactionHashesInner::Full(txs.iter()),
256            BlockTransactions::Uncle => BlockTransactionHashesInner::Uncle,
257        })
258    }
259}
260
261impl<T: TransactionResponse> Iterator for BlockTransactionHashes<'_, T> {
262    type Item = B256;
263
264    #[inline]
265    fn next(&mut self) -> Option<Self::Item> {
266        match &mut self.0 {
267            BlockTransactionHashesInner::Hashes(txs) => txs.next().copied(),
268            BlockTransactionHashesInner::Full(txs) => txs.next().map(|tx| tx.tx_hash()),
269            BlockTransactionHashesInner::Uncle => None,
270        }
271    }
272
273    #[inline]
274    fn size_hint(&self) -> (usize, Option<usize>) {
275        match &self.0 {
276            BlockTransactionHashesInner::Full(txs) => txs.size_hint(),
277            BlockTransactionHashesInner::Hashes(txs) => txs.size_hint(),
278            BlockTransactionHashesInner::Uncle => (0, Some(0)),
279        }
280    }
281}
282
283impl<T: TransactionResponse> ExactSizeIterator for BlockTransactionHashes<'_, T> {
284    #[inline]
285    fn len(&self) -> usize {
286        match &self.0 {
287            BlockTransactionHashesInner::Full(txs) => txs.len(),
288            BlockTransactionHashesInner::Hashes(txs) => txs.len(),
289            BlockTransactionHashesInner::Uncle => 0,
290        }
291    }
292}
293
294impl<T: TransactionResponse> DoubleEndedIterator for BlockTransactionHashes<'_, T> {
295    #[inline]
296    fn next_back(&mut self) -> Option<Self::Item> {
297        match &mut self.0 {
298            BlockTransactionHashesInner::Full(txs) => txs.next_back().map(|tx| tx.tx_hash()),
299            BlockTransactionHashesInner::Hashes(txs) => txs.next_back().copied(),
300            BlockTransactionHashesInner::Uncle => None,
301        }
302    }
303}
304
305#[cfg(feature = "std")]
306impl<T: TransactionResponse> std::iter::FusedIterator for BlockTransactionHashes<'_, T> {}
307
308/// Determines how the `transactions` field of block should be filled.
309///
310/// This essentially represents the `full:bool` argument in RPC calls that determine whether the
311/// response should include full transaction objects or just the hashes.
312#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
313#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
314pub enum BlockTransactionsKind {
315    /// Only include hashes: [BlockTransactions::Hashes]
316    #[default]
317    Hashes,
318    /// Include full transaction objects: [BlockTransactions::Full]
319    Full,
320}
321
322impl BlockTransactionsKind {
323    /// Returns true if this is [`BlockTransactionsKind::Hashes`]
324    pub const fn is_hashes(&self) -> bool {
325        matches!(self, Self::Hashes)
326    }
327
328    /// Returns true if this is [`BlockTransactionsKind::Full`]
329    pub const fn is_full(&self) -> bool {
330        matches!(self, Self::Full)
331    }
332}
333
334impl From<bool> for BlockTransactionsKind {
335    fn from(is_full: bool) -> Self {
336        if is_full {
337            Self::Full
338        } else {
339            Self::Hashes
340        }
341    }
342}
343
344impl From<BlockTransactionsKind> for bool {
345    fn from(kind: BlockTransactionsKind) -> Self {
346        match kind {
347            BlockTransactionsKind::Full => true,
348            BlockTransactionsKind::Hashes => false,
349        }
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    fn test_full_conversion() {
359        let full = true;
360        assert_eq!(BlockTransactionsKind::Full, full.into());
361
362        let full = false;
363        assert_eq!(BlockTransactionsKind::Hashes, full.into());
364    }
365
366    #[test]
367    fn test_block_transactions_default() {
368        let default: BlockTransactions<()> = BlockTransactions::default();
369        assert!(default.is_hashes());
370        assert_eq!(default.len(), 0);
371    }
372
373    #[test]
374    fn test_block_transactions_is_methods() {
375        let hashes: BlockTransactions<()> = BlockTransactions::Hashes(vec![B256::ZERO]);
376        let full: BlockTransactions<u32> = BlockTransactions::Full(vec![42]);
377        let uncle: BlockTransactions<()> = BlockTransactions::Uncle;
378
379        assert!(hashes.is_hashes());
380        assert!(!hashes.is_full());
381        assert!(!hashes.is_uncle());
382
383        assert!(full.is_full());
384        assert!(!full.is_hashes());
385        assert!(!full.is_uncle());
386
387        assert!(uncle.is_uncle());
388        assert!(!uncle.is_full());
389        assert!(!uncle.is_hashes());
390    }
391
392    #[test]
393    fn test_as_hashes() {
394        let hashes = vec![B256::ZERO, B256::repeat_byte(1)];
395        let tx_hashes: BlockTransactions<()> = BlockTransactions::Hashes(hashes.clone());
396
397        assert_eq!(tx_hashes.as_hashes(), Some(hashes.as_slice()));
398    }
399
400    #[test]
401    fn test_as_transactions() {
402        let transactions = vec![42, 43];
403        let txs = BlockTransactions::Full(transactions.clone());
404
405        assert_eq!(txs.as_transactions(), Some(transactions.as_slice()));
406    }
407
408    #[test]
409    fn test_block_transactions_len_and_is_empty() {
410        let hashes: BlockTransactions<()> = BlockTransactions::Hashes(vec![B256::ZERO]);
411        let full = BlockTransactions::Full(vec![42]);
412        let uncle: BlockTransactions<()> = BlockTransactions::Uncle;
413
414        assert_eq!(hashes.len(), 1);
415        assert_eq!(full.len(), 1);
416        assert_eq!(uncle.len(), 0);
417
418        assert!(!hashes.is_empty());
419        assert!(!full.is_empty());
420        assert!(uncle.is_empty());
421    }
422
423    #[test]
424    fn test_block_transactions_txns_iterator() {
425        let transactions = vec![42, 43];
426        let txs = BlockTransactions::Full(transactions);
427        let mut iter = txs.txns();
428
429        assert_eq!(iter.next(), Some(&42));
430        assert_eq!(iter.next(), Some(&43));
431        assert_eq!(iter.next(), None);
432    }
433
434    #[test]
435    fn test_block_transactions_into_transactions() {
436        let transactions = vec![42, 43];
437        let txs = BlockTransactions::Full(transactions.clone());
438        let collected: Vec<_> = txs.into_transactions().collect();
439
440        assert_eq!(collected, transactions);
441    }
442
443    #[test]
444    fn test_block_transactions_kind_conversion() {
445        let full: BlockTransactionsKind = true.into();
446        assert_eq!(full, BlockTransactionsKind::Full);
447
448        let hashes: BlockTransactionsKind = false.into();
449        assert_eq!(hashes, BlockTransactionsKind::Hashes);
450
451        let bool_full: bool = BlockTransactionsKind::Full.into();
452        assert!(bool_full);
453
454        let bool_hashes: bool = BlockTransactionsKind::Hashes.into();
455        assert!(!bool_hashes);
456    }
457}