Skip to main content

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