alloy_network_primitives/
block.rs

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