Skip to main content

reth_eth_wire_types/
broadcast.rs

1//! Types for broadcasting new data.
2
3use crate::{EthMessage, EthVersion, NetworkPrimitives};
4use alloc::{sync::Arc, vec::Vec};
5use alloy_primitives::{
6    map::{HashMap, HashSet},
7    Bytes, TxHash, B128, B256, U128,
8};
9use alloy_rlp::{
10    Decodable, Encodable, Header, RlpDecodable, RlpDecodableWrapper, RlpEncodable,
11    RlpEncodableWrapper,
12};
13use core::{fmt::Debug, mem};
14use derive_more::{Constructor, Deref, DerefMut, From, IntoIterator};
15use reth_codecs_derive::{add_arbitrary_tests, generate_tests};
16use reth_ethereum_primitives::TransactionSigned;
17use reth_primitives_traits::{Block, InMemorySize, SignedTransaction};
18
19/// This informs peers of new blocks that have appeared on the network.
20#[derive(
21    Clone,
22    Debug,
23    PartialEq,
24    Eq,
25    RlpEncodableWrapper,
26    RlpDecodableWrapper,
27    Default,
28    Deref,
29    DerefMut,
30    IntoIterator,
31)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
34#[add_arbitrary_tests(rlp)]
35pub struct NewBlockHashes(
36    /// New block hashes and the block number for each blockhash.
37    /// Clients should request blocks using a [`GetBlockBodies`](crate::GetBlockBodies) message.
38    pub Vec<BlockHashNumber>,
39);
40
41// === impl NewBlockHashes ===
42
43impl NewBlockHashes {
44    /// Returns the latest block in the list of blocks.
45    pub fn latest(&self) -> Option<&BlockHashNumber> {
46        self.iter().max_by_key(|b| b.number)
47    }
48}
49
50/// A block hash _and_ a block number.
51#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
54#[add_arbitrary_tests(rlp)]
55pub struct BlockHashNumber {
56    /// The block hash
57    pub hash: B256,
58    /// The block number
59    pub number: u64,
60}
61
62impl From<Vec<BlockHashNumber>> for NewBlockHashes {
63    fn from(v: Vec<BlockHashNumber>) -> Self {
64        Self(v)
65    }
66}
67
68impl From<NewBlockHashes> for Vec<BlockHashNumber> {
69    fn from(v: NewBlockHashes) -> Self {
70        v.0
71    }
72}
73
74/// A trait for block payloads transmitted through p2p.
75pub trait NewBlockPayload:
76    Encodable + Decodable + Clone + Eq + Debug + Send + Sync + Unpin + 'static
77{
78    /// The block type.
79    type Block: Block;
80
81    /// Returns a reference to the block.
82    fn block(&self) -> &Self::Block;
83}
84
85/// A new block with the current total difficulty, which includes the difficulty of the returned
86/// block.
87#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
90pub struct NewBlock<B = reth_ethereum_primitives::Block> {
91    /// A new block.
92    pub block: B,
93    /// The current total difficulty.
94    pub td: U128,
95}
96
97impl<B: Block + 'static> NewBlockPayload for NewBlock<B> {
98    type Block = B;
99
100    fn block(&self) -> &Self::Block {
101        &self.block
102    }
103}
104
105generate_tests!(#[rlp, 25] NewBlock<reth_ethereum_primitives::Block>, EthNewBlockTests);
106
107/// This informs peers of transactions that have appeared on the network and are not yet included
108/// in a block.
109#[derive(
110    Clone,
111    Debug,
112    PartialEq,
113    Eq,
114    RlpEncodableWrapper,
115    RlpDecodableWrapper,
116    Default,
117    Deref,
118    IntoIterator,
119)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
122#[add_arbitrary_tests(rlp, 10)]
123pub struct Transactions<T = TransactionSigned>(
124    /// New transactions for the peer to include in its mempool.
125    pub Vec<T>,
126);
127
128impl<T: SignedTransaction> Transactions<T> {
129    /// Returns `true` if the list of transactions contains any blob transactions.
130    pub fn has_eip4844(&self) -> bool {
131        self.iter().any(|tx| tx.is_eip4844())
132    }
133}
134
135impl<T> From<Vec<T>> for Transactions<T> {
136    fn from(txs: Vec<T>) -> Self {
137        Self(txs)
138    }
139}
140
141impl<T> From<Transactions<T>> for Vec<T> {
142    fn from(txs: Transactions<T>) -> Self {
143        txs.0
144    }
145}
146
147impl<T: Decodable + InMemorySize> Transactions<T> {
148    /// Decodes the RLP list of transactions, stopping once the cumulative
149    /// [`InMemorySize`] of decoded transactions exceeds `memory_budget` bytes.
150    /// Any remaining transactions in the payload are skipped.
151    pub fn decode_with_memory_budget(
152        buf: &mut &[u8],
153        memory_budget: usize,
154    ) -> alloy_rlp::Result<Self> {
155        decode_list_with_memory_budget(buf, memory_budget).map(Self)
156    }
157}
158
159/// Decodes an RLP list, stopping once the cumulative [`InMemorySize`] of decoded items exceeds
160/// `memory_budget` bytes. Any remaining items in the payload are skipped.
161pub fn decode_list_with_memory_budget<T: Decodable + InMemorySize>(
162    buf: &mut &[u8],
163    memory_budget: usize,
164) -> alloy_rlp::Result<Vec<T>> {
165    let header = Header::decode(buf)?;
166    if !header.list {
167        return Err(alloy_rlp::Error::UnexpectedString);
168    }
169    if buf.len() < header.payload_length {
170        return Err(alloy_rlp::Error::InputTooShort);
171    }
172
173    let (payload, rest) = buf.split_at(header.payload_length);
174    let mut payload = payload;
175
176    let mut txs = Vec::new();
177    let mut total_size = 0usize;
178
179    while !payload.is_empty() {
180        let item = T::decode(&mut payload)?;
181        total_size = total_size.saturating_add(item.size());
182
183        if total_size > memory_budget {
184            break;
185        }
186
187        txs.push(item);
188    }
189
190    *buf = rest;
191    Ok(txs)
192}
193
194/// Same as [`Transactions`] but this is intended as egress message send from local to _many_ peers.
195///
196/// The list of transactions is constructed on per-peers basis, but the underlying transaction
197/// objects are shared.
198#[derive(
199    Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Deref, IntoIterator,
200)]
201#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
202#[add_arbitrary_tests(rlp, 20)]
203pub struct SharedTransactions<T = TransactionSigned>(
204    /// New transactions for the peer to include in its mempool.
205    pub Vec<Arc<T>>,
206);
207
208/// A wrapper type for all different new pooled transaction types
209#[derive(Clone, Debug, PartialEq, Eq)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211pub enum NewPooledTransactionHashes {
212    /// A list of transaction hashes valid for [66-68)
213    Eth66(NewPooledTransactionHashes66),
214    /// A list of transaction hashes valid from [68..]
215    ///
216    /// Note: it is assumed that the payload is valid (all vectors have the same length)
217    Eth68(NewPooledTransactionHashes68),
218}
219
220// === impl NewPooledTransactionHashes ===
221
222impl NewPooledTransactionHashes {
223    /// Returns the message [`EthVersion`].
224    pub const fn version(&self) -> EthVersion {
225        match self {
226            Self::Eth66(_) => EthVersion::Eth66,
227            Self::Eth68(_) => EthVersion::Eth68,
228        }
229    }
230
231    /// Returns `true` if the payload is valid for the given version
232    pub const fn is_valid_for_version(&self, version: EthVersion) -> bool {
233        match self {
234            Self::Eth66(_) => {
235                matches!(version, EthVersion::Eth67 | EthVersion::Eth66)
236            }
237            Self::Eth68(_) => {
238                matches!(
239                    version,
240                    EthVersion::Eth68 |
241                        EthVersion::Eth69 |
242                        EthVersion::Eth70 |
243                        EthVersion::Eth71 |
244                        EthVersion::Eth72
245                )
246            }
247        }
248    }
249
250    /// Returns an iterator over all transaction hashes.
251    pub fn iter_hashes(&self) -> impl Iterator<Item = &B256> + '_ {
252        match self {
253            Self::Eth66(msg) => msg.iter(),
254            Self::Eth68(msg) => msg.hashes.iter(),
255        }
256    }
257
258    /// Returns an immutable reference to transaction hashes.
259    pub const fn hashes(&self) -> &Vec<B256> {
260        match self {
261            Self::Eth66(msg) => &msg.0,
262            Self::Eth68(msg) => &msg.hashes,
263        }
264    }
265
266    /// Returns a mutable reference to transaction hashes.
267    pub const fn hashes_mut(&mut self) -> &mut Vec<B256> {
268        match self {
269            Self::Eth66(msg) => &mut msg.0,
270            Self::Eth68(msg) => &mut msg.hashes,
271        }
272    }
273
274    /// Consumes the type and returns all hashes
275    pub fn into_hashes(self) -> Vec<B256> {
276        match self {
277            Self::Eth66(msg) => msg.0,
278            Self::Eth68(msg) => msg.hashes,
279        }
280    }
281
282    /// Returns an iterator over all transaction hashes.
283    pub fn into_iter_hashes(self) -> impl Iterator<Item = B256> {
284        match self {
285            Self::Eth66(msg) => msg.into_iter(),
286            Self::Eth68(msg) => msg.hashes.into_iter(),
287        }
288    }
289
290    /// Shortens the number of hashes in the message, keeping the first `len` hashes and dropping
291    /// the rest. If `len` is greater than the number of hashes, this has no effect.
292    pub fn truncate(&mut self, len: usize) {
293        match self {
294            Self::Eth66(msg) => msg.truncate(len),
295            Self::Eth68(msg) => {
296                msg.types.truncate(len);
297                msg.sizes.truncate(len);
298                msg.hashes.truncate(len);
299            }
300        }
301    }
302
303    /// Returns true if the message is empty
304    pub const fn is_empty(&self) -> bool {
305        match self {
306            Self::Eth66(msg) => msg.0.is_empty(),
307            Self::Eth68(msg) => msg.hashes.is_empty(),
308        }
309    }
310
311    /// Returns the number of hashes in the message
312    pub const fn len(&self) -> usize {
313        match self {
314            Self::Eth66(msg) => msg.0.len(),
315            Self::Eth68(msg) => msg.hashes.len(),
316        }
317    }
318
319    /// Returns an immutable reference to the inner type if this is an eth68 announcement.
320    pub const fn as_eth68(&self) -> Option<&NewPooledTransactionHashes68> {
321        match self {
322            Self::Eth66(_) => None,
323            Self::Eth68(msg) => Some(msg),
324        }
325    }
326
327    /// Returns a mutable reference to the inner type if this is an eth68 announcement.
328    pub const fn as_eth68_mut(&mut self) -> Option<&mut NewPooledTransactionHashes68> {
329        match self {
330            Self::Eth66(_) => None,
331            Self::Eth68(msg) => Some(msg),
332        }
333    }
334
335    /// Returns a mutable reference to the inner type if this is an eth66 announcement.
336    pub const fn as_eth66_mut(&mut self) -> Option<&mut NewPooledTransactionHashes66> {
337        match self {
338            Self::Eth66(msg) => Some(msg),
339            Self::Eth68(_) => None,
340        }
341    }
342
343    /// Returns the inner type if this is an eth68 announcement.
344    pub fn take_eth68(&mut self) -> Option<NewPooledTransactionHashes68> {
345        match self {
346            Self::Eth66(_) => None,
347            Self::Eth68(msg) => Some(mem::take(msg)),
348        }
349    }
350
351    /// Returns the inner type if this is an eth66 announcement.
352    pub fn take_eth66(&mut self) -> Option<NewPooledTransactionHashes66> {
353        match self {
354            Self::Eth66(msg) => Some(mem::take(msg)),
355            Self::Eth68(_) => None,
356        }
357    }
358}
359
360impl<N: NetworkPrimitives> From<NewPooledTransactionHashes> for EthMessage<N> {
361    fn from(value: NewPooledTransactionHashes) -> Self {
362        match value {
363            NewPooledTransactionHashes::Eth66(msg) => Self::NewPooledTransactionHashes66(msg),
364            NewPooledTransactionHashes::Eth68(msg) => Self::NewPooledTransactionHashes68(msg),
365        }
366    }
367}
368
369impl From<NewPooledTransactionHashes66> for NewPooledTransactionHashes {
370    fn from(hashes: NewPooledTransactionHashes66) -> Self {
371        Self::Eth66(hashes)
372    }
373}
374
375impl From<NewPooledTransactionHashes68> for NewPooledTransactionHashes {
376    fn from(hashes: NewPooledTransactionHashes68) -> Self {
377        Self::Eth68(hashes)
378    }
379}
380
381/// This informs peers of transaction hashes for transactions that have appeared on the network,
382/// but have not been included in a block.
383#[derive(
384    Clone,
385    Debug,
386    PartialEq,
387    Eq,
388    RlpEncodableWrapper,
389    RlpDecodableWrapper,
390    Default,
391    Deref,
392    DerefMut,
393    IntoIterator,
394)]
395#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
396#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
397#[add_arbitrary_tests(rlp)]
398pub struct NewPooledTransactionHashes66(
399    /// Transaction hashes for new transactions that have appeared on the network.
400    /// Clients should request the transactions with the given hashes using a
401    /// [`GetPooledTransactions`](crate::GetPooledTransactions) message.
402    pub Vec<B256>,
403);
404
405impl From<Vec<B256>> for NewPooledTransactionHashes66 {
406    fn from(v: Vec<B256>) -> Self {
407        Self(v)
408    }
409}
410
411/// Same as [`NewPooledTransactionHashes66`] but extends that beside the transaction hashes,
412/// the node sends the transaction types and their sizes (as defined in EIP-2718) as well.
413#[derive(Clone, Debug, PartialEq, Eq, Default)]
414#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
415pub struct NewPooledTransactionHashes68 {
416    /// Transaction types for new transactions that have appeared on the network.
417    ///
418    /// ## Note on RLP encoding and decoding
419    ///
420    /// In the [eth/68 spec](https://eips.ethereum.org/EIPS/eip-5793#specification) this is defined
421    /// the following way:
422    ///  * `[type_0: B_1, type_1: B_1, ...]`
423    ///
424    /// This would make it seem like the [`Encodable`] and
425    /// [`Decodable`] implementations should directly use a `Vec<u8>` for
426    /// encoding and decoding, because it looks like this field should be encoded as a _list_ of
427    /// bytes.
428    ///
429    /// However, [this is implemented in geth as a `[]byte`
430    /// type](https://github.com/ethereum/go-ethereum/blob/82d934b1dd80cdd8190803ea9f73ed2c345e2576/eth/protocols/eth/protocol.go#L308-L313),
431    /// which [ends up being encoded as a RLP
432    /// string](https://github.com/ethereum/go-ethereum/blob/82d934b1dd80cdd8190803ea9f73ed2c345e2576/rlp/encode_test.go#L171-L176),
433    /// **not** a RLP list.
434    ///
435    /// Because of this, we do not directly use the `Vec<u8>` when encoding and decoding, and
436    /// instead use the [`Encodable`] and [`Decodable`]
437    /// implementations for `&[u8]` instead, which encodes into a RLP string, and expects an RLP
438    /// string when decoding.
439    pub types: Vec<u8>,
440    /// Transaction sizes for new transactions that have appeared on the network.
441    pub sizes: Vec<usize>,
442    /// Transaction hashes for new transactions that have appeared on the network.
443    pub hashes: Vec<B256>,
444}
445
446#[cfg(feature = "arbitrary")]
447impl proptest::prelude::Arbitrary for NewPooledTransactionHashes68 {
448    type Parameters = ();
449    fn arbitrary_with(_args: ()) -> Self::Strategy {
450        use proptest::{collection::vec, prelude::*};
451        // Generate a single random length for all vectors
452        let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); // Lengths between 1 and 100
453
454        vec_length
455            .prop_flat_map(|len| {
456                // Use the generated length to create vectors of TxType, usize, and B256
457                let types_vec = vec(
458                    proptest_arbitrary_interop::arb::<reth_ethereum_primitives::TxType>()
459                        .prop_map(|ty| ty as u8),
460                    len..=len,
461                );
462
463                // Map the usize values to the range 0..131072(0x20000)
464                let sizes_vec = vec(proptest::num::usize::ANY.prop_map(|x| x % 131072), len..=len);
465                let hashes_vec = vec(any::<B256>(), len..=len);
466
467                (types_vec, sizes_vec, hashes_vec)
468            })
469            .prop_map(|(types, sizes, hashes)| Self { types, sizes, hashes })
470            .boxed()
471    }
472
473    type Strategy = proptest::prelude::BoxedStrategy<Self>;
474}
475
476impl NewPooledTransactionHashes68 {
477    /// Returns an iterator over tx hashes zipped with corresponding metadata.
478    pub fn metadata_iter(&self) -> impl Iterator<Item = (&B256, (u8, usize))> {
479        self.hashes.iter().zip(self.types.iter().copied().zip(self.sizes.iter().copied()))
480    }
481
482    /// Appends a transaction
483    pub fn push<T: SignedTransaction>(&mut self, tx: &T) {
484        self.hashes.push(*tx.tx_hash());
485        self.sizes.push(tx.encode_2718_len());
486        self.types.push(tx.ty());
487    }
488
489    /// Appends the provided transactions
490    pub fn extend<'a, T: SignedTransaction>(&mut self, txs: impl IntoIterator<Item = &'a T>) {
491        for tx in txs {
492            self.push(tx);
493        }
494    }
495
496    /// Shrinks the capacity of the message vectors as much as possible.
497    pub fn shrink_to_fit(&mut self) {
498        self.hashes.shrink_to_fit();
499        self.sizes.shrink_to_fit();
500        self.types.shrink_to_fit()
501    }
502
503    /// Consumes and appends a transaction
504    pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
505        self.push(tx);
506        self
507    }
508
509    /// Consumes and appends the provided transactions
510    pub fn with_transactions<'a, T: SignedTransaction>(
511        mut self,
512        txs: impl IntoIterator<Item = &'a T>,
513    ) -> Self {
514        self.extend(txs);
515        self
516    }
517}
518
519impl Encodable for NewPooledTransactionHashes68 {
520    fn encode(&self, out: &mut dyn bytes::BufMut) {
521        #[derive(RlpEncodable)]
522        struct EncodableNewPooledTransactionHashes68<'a> {
523            types: &'a [u8],
524            sizes: &'a Vec<usize>,
525            hashes: &'a Vec<B256>,
526        }
527
528        let encodable = EncodableNewPooledTransactionHashes68 {
529            types: &self.types[..],
530            sizes: &self.sizes,
531            hashes: &self.hashes,
532        };
533
534        encodable.encode(out);
535    }
536    fn length(&self) -> usize {
537        #[derive(RlpEncodable)]
538        struct EncodableNewPooledTransactionHashes68<'a> {
539            types: &'a [u8],
540            sizes: &'a Vec<usize>,
541            hashes: &'a Vec<B256>,
542        }
543
544        let encodable = EncodableNewPooledTransactionHashes68 {
545            types: &self.types[..],
546            sizes: &self.sizes,
547            hashes: &self.hashes,
548        };
549
550        encodable.length()
551    }
552}
553
554impl Decodable for NewPooledTransactionHashes68 {
555    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
556        #[derive(RlpDecodable)]
557        struct EncodableNewPooledTransactionHashes68 {
558            types: Bytes,
559            sizes: Vec<usize>,
560            hashes: Vec<B256>,
561        }
562
563        let encodable = EncodableNewPooledTransactionHashes68::decode(buf)?;
564        let msg = Self {
565            types: encodable.types.into(),
566            sizes: encodable.sizes,
567            hashes: encodable.hashes,
568        };
569
570        if msg.hashes.len() != msg.types.len() {
571            return Err(alloy_rlp::Error::ListLengthMismatch {
572                expected: msg.hashes.len(),
573                got: msg.types.len(),
574            })
575        }
576        if msg.hashes.len() != msg.sizes.len() {
577            return Err(alloy_rlp::Error::ListLengthMismatch {
578                expected: msg.hashes.len(),
579                got: msg.sizes.len(),
580            })
581        }
582
583        Ok(msg)
584    }
585}
586
587/// Same as [`NewPooledTransactionHashes68`] but adds cell mask of B128.
588#[derive(Clone, Debug, PartialEq, Eq, Default)]
589#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
590pub struct NewPooledTransactionHashes72 {
591    /// Transaction types for new transactions that have appeared on the network.
592    ///
593    /// ## Note on RLP encoding and decoding
594    ///
595    /// In the [eth/72 spec](https://eips.ethereum.org/EIPS/eip-8070#specification) this is defined
596    /// the following way:
597    ///  * `[types: B, [size_0: P, size_1: P, ...], [hash_0: B_32, hash_1: B_32, ...], cell_mask:
598    ///    B_16]`
599    pub types: Vec<u8>,
600    /// Transaction sizes for new transactions that have appeared on the network.
601    pub sizes: Vec<usize>,
602    /// Transaction hashes for new transactions that have appeared on the network.
603    pub hashes: Vec<B256>,
604    /// Cell mask for new transactions that have appeared on the network.
605    pub cell_mask: Option<B128>,
606}
607
608#[cfg(feature = "arbitrary")]
609impl proptest::prelude::Arbitrary for NewPooledTransactionHashes72 {
610    type Parameters = ();
611    fn arbitrary_with(_args: ()) -> Self::Strategy {
612        use proptest::{collection::vec, prelude::*};
613        // Generate a single random length for all vectors
614        let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); // Lengths between 1 and 100
615
616        vec_length
617            .prop_flat_map(|len| {
618                // Use the generated length to create vectors of TxType, usize, and B256
619                let types_vec = vec(
620                    proptest_arbitrary_interop::arb::<reth_ethereum_primitives::TxType>()
621                        .prop_map(|ty| ty as u8),
622                    len..=len,
623                );
624
625                // Map the usize values to the range 0..131072(0x20000)
626                let sizes_vec = vec(proptest::num::usize::ANY.prop_map(|x| x % 131072), len..=len);
627                let hashes_vec = vec(any::<B256>(), len..=len);
628                let cell_mask = any::<Option<B128>>();
629
630                (types_vec, sizes_vec, hashes_vec, cell_mask)
631            })
632            .prop_map(|(types, sizes, hashes, cell_mask)| Self { types, sizes, hashes, cell_mask })
633            .boxed()
634    }
635
636    type Strategy = proptest::prelude::BoxedStrategy<Self>;
637}
638
639impl NewPooledTransactionHashes72 {
640    /// Returns an iterator over tx hashes zipped with corresponding metadata.
641    pub fn metadata_iter(&self) -> impl Iterator<Item = (&B256, (u8, usize))> {
642        self.hashes.iter().zip(self.types.iter().copied().zip(self.sizes.iter().copied()))
643    }
644
645    /// Appends a transaction
646    pub fn push<T: SignedTransaction>(&mut self, tx: &T) {
647        self.hashes.push(*tx.tx_hash());
648        self.sizes.push(tx.encode_2718_len());
649        self.types.push(tx.ty());
650    }
651
652    /// Appends the provided transactions
653    pub fn extend<'a, T: SignedTransaction>(&mut self, txs: impl IntoIterator<Item = &'a T>) {
654        for tx in txs {
655            self.push(tx);
656        }
657    }
658
659    /// Shrinks the capacity of the message vectors as much as possible.
660    pub fn shrink_to_fit(&mut self) {
661        self.hashes.shrink_to_fit();
662        self.sizes.shrink_to_fit();
663        self.types.shrink_to_fit()
664    }
665
666    /// Consumes and appends a transaction
667    pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
668        self.push(tx);
669        self
670    }
671
672    /// Consumes and appends the provided transactions
673    pub fn with_transactions<'a, T: SignedTransaction>(
674        mut self,
675        txs: impl IntoIterator<Item = &'a T>,
676    ) -> Self {
677        self.extend(txs);
678        self
679    }
680
681    fn payload_length(&self) -> usize {
682        self.types.as_slice().length() +
683            self.sizes.length() +
684            self.hashes.length() +
685            self.cell_mask.as_ref().map_or(1, Encodable::length)
686    }
687}
688
689impl Encodable for NewPooledTransactionHashes72 {
690    fn encode(&self, out: &mut dyn bytes::BufMut) {
691        Header { list: true, payload_length: self.payload_length() }.encode(out);
692        self.types.as_slice().encode(out);
693        self.sizes.encode(out);
694        self.hashes.encode(out);
695        if let Some(cell_mask) = &self.cell_mask {
696            cell_mask.encode(out);
697        } else {
698            out.put_u8(alloy_rlp::EMPTY_STRING_CODE);
699        }
700    }
701
702    fn length(&self) -> usize {
703        Header { list: true, payload_length: self.payload_length() }.length_with_payload()
704    }
705}
706
707impl Decodable for NewPooledTransactionHashes72 {
708    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
709        let Header { list, payload_length } = Header::decode(buf)?;
710        if !list {
711            return Err(alloy_rlp::Error::UnexpectedString)
712        }
713        if buf.len() < payload_length {
714            return Err(alloy_rlp::Error::InputTooShort)
715        }
716
717        let (mut payload, rest) = buf.split_at(payload_length);
718        let types = Bytes::decode(&mut payload)?;
719        let sizes = Vec::<usize>::decode(&mut payload)?;
720        let hashes = Vec::<B256>::decode(&mut payload)?;
721        let Some(first_byte) = payload.first().copied() else {
722            return Err(alloy_rlp::Error::InputTooShort)
723        };
724        let cell_mask = if first_byte == alloy_rlp::EMPTY_STRING_CODE {
725            payload = &payload[1..];
726            None
727        } else {
728            Some(B128::decode(&mut payload)?)
729        };
730
731        if !payload.is_empty() {
732            return Err(alloy_rlp::Error::ListLengthMismatch {
733                expected: payload_length,
734                got: payload_length - payload.len(),
735            })
736        }
737
738        let msg = Self { types: types.into(), sizes, hashes, cell_mask };
739
740        if msg.hashes.len() != msg.types.len() {
741            return Err(alloy_rlp::Error::ListLengthMismatch {
742                expected: msg.hashes.len(),
743                got: msg.types.len(),
744            })
745        }
746        if msg.hashes.len() != msg.sizes.len() {
747            return Err(alloy_rlp::Error::ListLengthMismatch {
748                expected: msg.hashes.len(),
749                got: msg.sizes.len(),
750            })
751        }
752
753        *buf = rest;
754
755        Ok(msg)
756    }
757}
758
759/// Validation pass that checks for unique transaction hashes.
760pub trait DedupPayload {
761    /// Value type in [`PartiallyValidData`] map.
762    type Value;
763
764    /// The payload contains no entries.
765    fn is_empty(&self) -> bool;
766
767    /// Returns the number of entries.
768    fn len(&self) -> usize;
769
770    /// Consumes self, returning an iterator over hashes in payload.
771    fn dedup(self) -> PartiallyValidData<Self::Value>;
772}
773
774/// Value in [`PartiallyValidData`] map obtained from an announcement.
775pub type Eth68TxMetadata = Option<(u8, usize)>;
776
777impl DedupPayload for NewPooledTransactionHashes {
778    type Value = Eth68TxMetadata;
779
780    fn is_empty(&self) -> bool {
781        self.is_empty()
782    }
783
784    fn len(&self) -> usize {
785        self.len()
786    }
787
788    fn dedup(self) -> PartiallyValidData<Self::Value> {
789        match self {
790            Self::Eth66(msg) => msg.dedup(),
791            Self::Eth68(msg) => msg.dedup(),
792        }
793    }
794}
795
796impl DedupPayload for NewPooledTransactionHashes68 {
797    type Value = Eth68TxMetadata;
798
799    fn is_empty(&self) -> bool {
800        self.hashes.is_empty()
801    }
802
803    fn len(&self) -> usize {
804        self.hashes.len()
805    }
806
807    fn dedup(self) -> PartiallyValidData<Self::Value> {
808        let Self { hashes, mut sizes, mut types } = self;
809
810        let mut deduped_data = HashMap::with_capacity_and_hasher(hashes.len(), Default::default());
811
812        for hash in hashes.into_iter().rev() {
813            if let (Some(ty), Some(size)) = (types.pop(), sizes.pop()) {
814                deduped_data.insert(hash, Some((ty, size)));
815            }
816        }
817
818        PartiallyValidData::from_raw_data_eth68(deduped_data)
819    }
820}
821
822impl DedupPayload for NewPooledTransactionHashes66 {
823    type Value = Eth68TxMetadata;
824
825    fn is_empty(&self) -> bool {
826        self.0.is_empty()
827    }
828
829    fn len(&self) -> usize {
830        self.0.len()
831    }
832
833    fn dedup(self) -> PartiallyValidData<Self::Value> {
834        let Self(hashes) = self;
835
836        let mut deduped_data = HashMap::with_capacity_and_hasher(hashes.len(), Default::default());
837
838        let noop_value: Eth68TxMetadata = None;
839
840        for hash in hashes.into_iter().rev() {
841            deduped_data.insert(hash, noop_value);
842        }
843
844        PartiallyValidData::from_raw_data_eth66(deduped_data)
845    }
846}
847
848/// Interface for handling mempool message data. Used in various filters in pipelines in
849/// `TransactionsManager` and in queries to `TransactionPool`.
850pub trait HandleMempoolData {
851    /// The announcement contains no entries.
852    fn is_empty(&self) -> bool;
853
854    /// Returns the number of entries.
855    fn len(&self) -> usize;
856
857    /// Retain only entries for which the hash in the entry satisfies a given predicate.
858    fn retain_by_hash(&mut self, f: impl FnMut(&TxHash) -> bool);
859}
860
861/// Extension of [`HandleMempoolData`] interface, for mempool messages that are versioned.
862pub trait HandleVersionedMempoolData {
863    /// Returns the announcement version, either [`Eth66`](EthVersion::Eth66) or
864    /// [`Eth68`](EthVersion::Eth68).
865    fn msg_version(&self) -> EthVersion;
866}
867
868impl<T: SignedTransaction> HandleMempoolData for Vec<T> {
869    fn is_empty(&self) -> bool {
870        self.is_empty()
871    }
872
873    fn len(&self) -> usize {
874        self.len()
875    }
876
877    fn retain_by_hash(&mut self, mut f: impl FnMut(&TxHash) -> bool) {
878        self.retain(|tx| f(tx.tx_hash()))
879    }
880}
881
882macro_rules! handle_mempool_data_map_impl {
883    ($data_ty:ty, $(<$generic:ident>)?) => {
884        impl$(<$generic>)? HandleMempoolData for $data_ty {
885            fn is_empty(&self) -> bool {
886                self.data.is_empty()
887            }
888
889            fn len(&self) -> usize {
890                self.data.len()
891            }
892
893            fn retain_by_hash(&mut self, mut f: impl FnMut(&TxHash) -> bool) {
894                self.data.retain(|hash, _| f(hash));
895            }
896        }
897    };
898}
899
900/// Data that has passed an initial validation pass that is not specific to any mempool message
901/// type.
902#[derive(Debug, Deref, DerefMut, IntoIterator)]
903pub struct PartiallyValidData<V> {
904    #[deref]
905    #[deref_mut]
906    #[into_iterator]
907    data: HashMap<TxHash, V>,
908    version: Option<EthVersion>,
909}
910
911handle_mempool_data_map_impl!(PartiallyValidData<V>, <V>);
912
913impl<V> PartiallyValidData<V> {
914    /// Wraps raw data.
915    pub const fn from_raw_data(data: HashMap<TxHash, V>, version: Option<EthVersion>) -> Self {
916        Self { data, version }
917    }
918
919    /// Wraps raw data with version [`EthVersion::Eth68`].
920    pub const fn from_raw_data_eth68(data: HashMap<TxHash, V>) -> Self {
921        Self::from_raw_data(data, Some(EthVersion::Eth68))
922    }
923
924    /// Wraps raw data with version [`EthVersion::Eth66`].
925    pub const fn from_raw_data_eth66(data: HashMap<TxHash, V>) -> Self {
926        Self::from_raw_data(data, Some(EthVersion::Eth66))
927    }
928
929    /// Returns a new [`PartiallyValidData`] with empty data from an [`Eth68`](EthVersion::Eth68)
930    /// announcement.
931    pub fn empty_eth68() -> Self {
932        Self::from_raw_data_eth68(HashMap::default())
933    }
934
935    /// Returns a new [`PartiallyValidData`] with empty data from an [`Eth66`](EthVersion::Eth66)
936    /// announcement.
937    pub fn empty_eth66() -> Self {
938        Self::from_raw_data_eth66(HashMap::default())
939    }
940
941    /// Returns the version of the message this data was received in if different versions of the
942    /// message exists, either [`Eth66`](EthVersion::Eth66) or [`Eth68`](EthVersion::Eth68).
943    pub const fn msg_version(&self) -> Option<EthVersion> {
944        self.version
945    }
946
947    /// Destructs returning the validated data.
948    pub fn into_data(self) -> HashMap<TxHash, V> {
949        self.data
950    }
951}
952
953/// Partially validated data from an announcement or a
954/// [`PooledTransactions`](crate::PooledTransactions) response.
955#[derive(Debug, Deref, DerefMut, IntoIterator, From)]
956pub struct ValidAnnouncementData {
957    #[deref]
958    #[deref_mut]
959    #[into_iterator]
960    data: HashMap<TxHash, Eth68TxMetadata>,
961    version: EthVersion,
962}
963
964handle_mempool_data_map_impl!(ValidAnnouncementData,);
965
966impl ValidAnnouncementData {
967    /// Destructs returning only the valid hashes and the announcement message version. Caution! If
968    /// this is [`Eth68`](EthVersion::Eth68) announcement data, this drops the metadata.
969    pub fn into_request_hashes(self) -> (RequestTxHashes, EthVersion) {
970        let hashes = self.data.into_keys().collect::<HashSet<_>>();
971
972        (RequestTxHashes::new(hashes), self.version)
973    }
974
975    /// Conversion from [`PartiallyValidData`] from an announcement. Note! [`PartiallyValidData`]
976    /// from an announcement, should have some [`EthVersion`]. Panics if [`PartiallyValidData`] has
977    /// version set to `None`.
978    pub fn from_partially_valid_data(data: PartiallyValidData<Eth68TxMetadata>) -> Self {
979        let PartiallyValidData { data, version } = data;
980
981        let version = version.expect("should have eth version for conversion");
982
983        Self { data, version }
984    }
985
986    /// Destructs returning the validated data.
987    pub fn into_data(self) -> HashMap<TxHash, Eth68TxMetadata> {
988        self.data
989    }
990}
991
992impl HandleVersionedMempoolData for ValidAnnouncementData {
993    fn msg_version(&self) -> EthVersion {
994        self.version
995    }
996}
997
998/// Hashes to request from a peer.
999#[derive(Debug, Default, Deref, DerefMut, IntoIterator, Constructor)]
1000pub struct RequestTxHashes {
1001    #[deref]
1002    #[deref_mut]
1003    #[into_iterator(owned, ref)]
1004    hashes: HashSet<TxHash>,
1005}
1006
1007impl RequestTxHashes {
1008    /// Returns a new [`RequestTxHashes`] with given capacity for hashes. Caution! Make sure to
1009    /// call [`HashSet::shrink_to_fit`] on [`RequestTxHashes`] when full, especially where it will
1010    /// be stored in its entirety like in the future waiting for a
1011    /// [`GetPooledTransactions`](crate::GetPooledTransactions) request to resolve.
1012    pub fn with_capacity(capacity: usize) -> Self {
1013        Self::new(HashSet::with_capacity_and_hasher(capacity, Default::default()))
1014    }
1015
1016    /// Returns a new empty instance.
1017    fn empty() -> Self {
1018        Self::new(HashSet::default())
1019    }
1020
1021    /// Retains the given number of elements, returning an iterator over the rest.
1022    pub fn retain_count(&mut self, count: usize) -> Self {
1023        let rest_capacity = self.hashes.len().saturating_sub(count);
1024        if rest_capacity == 0 {
1025            return Self::empty()
1026        }
1027        let mut rest = Self::with_capacity(rest_capacity);
1028
1029        let mut i = 0;
1030        self.hashes.retain(|hash| {
1031            if i >= count {
1032                rest.insert(*hash);
1033                return false
1034            }
1035            i += 1;
1036
1037            true
1038        });
1039
1040        rest
1041    }
1042}
1043
1044impl FromIterator<(TxHash, Eth68TxMetadata)> for RequestTxHashes {
1045    fn from_iter<I: IntoIterator<Item = (TxHash, Eth68TxMetadata)>>(iter: I) -> Self {
1046        Self::new(iter.into_iter().map(|(hash, _)| hash).collect())
1047    }
1048}
1049
1050/// The earliest block, the latest block and hash of the latest block which can be provided.
1051/// See [BlockRangeUpdate](https://github.com/ethereum/devp2p/blob/master/caps/eth.md#blockrangeupdate-0x11).
1052#[derive(Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
1053#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1054#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1055pub struct BlockRangeUpdate {
1056    /// The earliest block which is available.
1057    pub earliest: u64,
1058    /// The latest block which is available.
1059    pub latest: u64,
1060    /// Latest available block's hash.
1061    pub latest_hash: B256,
1062}
1063
1064impl InMemorySize for NewPooledTransactionHashes {
1065    fn size(&self) -> usize {
1066        match self {
1067            Self::Eth66(msg) => msg.0.len() * core::mem::size_of::<B256>(),
1068            Self::Eth68(msg) => {
1069                msg.types.len() * core::mem::size_of::<u8>() +
1070                    msg.sizes.len() * core::mem::size_of::<usize>() +
1071                    msg.hashes.len() * core::mem::size_of::<B256>()
1072            }
1073        }
1074    }
1075}
1076
1077#[cfg(test)]
1078mod tests {
1079    use super::*;
1080    use alloy_consensus::{transaction::TxHashRef, Typed2718};
1081    use alloy_eips::eip2718::Encodable2718;
1082    use alloy_primitives::{b256, hex, Signature, U256};
1083    use reth_ethereum_primitives::{Transaction, TransactionSigned};
1084    use std::str::FromStr;
1085
1086    /// Takes as input a struct / encoded hex message pair, ensuring that we encode to the exact hex
1087    /// message, and decode to the exact struct.
1088    fn test_encoding_vector<T: Encodable + Decodable + PartialEq + core::fmt::Debug>(
1089        input: (T, &[u8]),
1090    ) {
1091        let (expected_decoded, expected_encoded) = input;
1092        let mut encoded = Vec::new();
1093        expected_decoded.encode(&mut encoded);
1094
1095        assert_eq!(hex::encode(&encoded), hex::encode(expected_encoded));
1096
1097        let decoded = T::decode(&mut encoded.as_ref()).unwrap();
1098        assert_eq!(expected_decoded, decoded);
1099    }
1100
1101    #[test]
1102    fn can_return_latest_block() {
1103        let mut blocks = NewBlockHashes(vec![BlockHashNumber { hash: B256::random(), number: 0 }]);
1104        let latest = blocks.latest().unwrap();
1105        assert_eq!(latest.number, 0);
1106
1107        blocks.push(BlockHashNumber { hash: B256::random(), number: 100 });
1108        blocks.push(BlockHashNumber { hash: B256::random(), number: 2 });
1109        let latest = blocks.latest().unwrap();
1110        assert_eq!(latest.number, 100);
1111    }
1112
1113    #[test]
1114    fn eth_68_tx_hash_roundtrip() {
1115        let vectors = vec![
1116            (
1117                NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] },
1118                &hex!("c380c0c0")[..],
1119            ),
1120            (
1121                NewPooledTransactionHashes68 {
1122                    types: vec![0x00],
1123                    sizes: vec![0x00],
1124                    hashes: vec![
1125                        B256::from_str(
1126                            "0x0000000000000000000000000000000000000000000000000000000000000000",
1127                        )
1128                        .unwrap(),
1129                    ],
1130                },
1131                &hex!(
1132                    "e500c180e1a00000000000000000000000000000000000000000000000000000000000000000"
1133                )[..],
1134            ),
1135            (
1136                NewPooledTransactionHashes68 {
1137                    types: vec![0x00, 0x00],
1138                    sizes: vec![0x00, 0x00],
1139                    hashes: vec![
1140                        B256::from_str(
1141                            "0x0000000000000000000000000000000000000000000000000000000000000000",
1142                        )
1143                        .unwrap(),
1144                        B256::from_str(
1145                            "0x0000000000000000000000000000000000000000000000000000000000000000",
1146                        )
1147                        .unwrap(),
1148                    ],
1149                },
1150                &hex!(
1151                    "f84a820000c28080f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
1152                )[..],
1153            ),
1154            (
1155                NewPooledTransactionHashes68 {
1156                    types: vec![0x02],
1157                    sizes: vec![0xb6],
1158                    hashes: vec![
1159                        B256::from_str(
1160                            "0xfecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124",
1161                        )
1162                        .unwrap(),
1163                    ],
1164                },
1165                &hex!(
1166                    "e602c281b6e1a0fecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124"
1167                )[..],
1168            ),
1169            (
1170                NewPooledTransactionHashes68 {
1171                    types: vec![0xff, 0xff],
1172                    sizes: vec![0xffffffff, 0xffffffff],
1173                    hashes: vec![
1174                        B256::from_str(
1175                            "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1176                        )
1177                        .unwrap(),
1178                        B256::from_str(
1179                            "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1180                        )
1181                        .unwrap(),
1182                    ],
1183                },
1184                &hex!(
1185                    "f85282ffffca84ffffffff84fffffffff842a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1186                )[..],
1187            ),
1188            (
1189                NewPooledTransactionHashes68 {
1190                    types: vec![0xff, 0xff],
1191                    sizes: vec![0xffffffff, 0xffffffff],
1192                    hashes: vec![
1193                        B256::from_str(
1194                            "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe",
1195                        )
1196                        .unwrap(),
1197                        B256::from_str(
1198                            "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe",
1199                        )
1200                        .unwrap(),
1201                    ],
1202                },
1203                &hex!(
1204                    "f85282ffffca84ffffffff84fffffffff842a0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafea0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe"
1205                )[..],
1206            ),
1207            (
1208                NewPooledTransactionHashes68 {
1209                    types: vec![0x10, 0x10],
1210                    sizes: vec![0xdeadc0de, 0xdeadc0de],
1211                    hashes: vec![
1212                        B256::from_str(
1213                            "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2",
1214                        )
1215                        .unwrap(),
1216                        B256::from_str(
1217                            "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2",
1218                        )
1219                        .unwrap(),
1220                    ],
1221                },
1222                &hex!(
1223                    "f852821010ca84deadc0de84deadc0def842a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2"
1224                )[..],
1225            ),
1226            (
1227                NewPooledTransactionHashes68 {
1228                    types: vec![0x6f, 0x6f],
1229                    sizes: vec![0x7fffffff, 0x7fffffff],
1230                    hashes: vec![
1231                        B256::from_str(
1232                            "0x0000000000000000000000000000000000000000000000000000000000000002",
1233                        )
1234                        .unwrap(),
1235                        B256::from_str(
1236                            "0x0000000000000000000000000000000000000000000000000000000000000002",
1237                        )
1238                        .unwrap(),
1239                    ],
1240                },
1241                &hex!(
1242                    "f852826f6fca847fffffff847ffffffff842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000002"
1243                )[..],
1244            ),
1245        ];
1246
1247        for vector in vectors {
1248            test_encoding_vector(vector);
1249        }
1250    }
1251
1252    #[test]
1253    fn eth_72_tx_hash_roundtrip() {
1254        let vectors = vec![
1255            (
1256                NewPooledTransactionHashes72 {
1257                    types: vec![],
1258                    sizes: vec![],
1259                    hashes: vec![],
1260                    cell_mask: None,
1261                },
1262                &hex!("c480c0c080")[..],
1263            ),
1264            (
1265                NewPooledTransactionHashes72 {
1266                    types: vec![],
1267                    sizes: vec![],
1268                    hashes: vec![],
1269                    cell_mask: Some(B128::repeat_byte(0x11)),
1270                },
1271                &hex!("d480c0c09011111111111111111111111111111111")[..],
1272            ),
1273        ];
1274
1275        for vector in vectors {
1276            test_encoding_vector(vector);
1277        }
1278    }
1279
1280    #[test]
1281    fn eth_72_rejects_missing_cell_mask() {
1282        let encoded_eth68_payload = hex!("c380c0c0");
1283
1284        let result = NewPooledTransactionHashes72::decode(&mut encoded_eth68_payload.as_ref());
1285
1286        assert!(matches!(result, Err(alloy_rlp::Error::InputTooShort)));
1287    }
1288
1289    #[test]
1290    fn request_hashes_retain_count_keep_subset() {
1291        let mut hashes = RequestTxHashes::new(
1292            [
1293                b256!("0x0000000000000000000000000000000000000000000000000000000000000001"),
1294                b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
1295                b256!("0x0000000000000000000000000000000000000000000000000000000000000003"),
1296                b256!("0x0000000000000000000000000000000000000000000000000000000000000004"),
1297                b256!("0x0000000000000000000000000000000000000000000000000000000000000005"),
1298            ]
1299            .into_iter()
1300            .collect::<HashSet<_>>(),
1301        );
1302
1303        let rest = hashes.retain_count(3);
1304
1305        assert_eq!(3, hashes.len());
1306        assert_eq!(2, rest.len());
1307    }
1308
1309    #[test]
1310    fn request_hashes_retain_count_keep_all() {
1311        let mut hashes = RequestTxHashes::new(
1312            [
1313                b256!("0x0000000000000000000000000000000000000000000000000000000000000001"),
1314                b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
1315                b256!("0x0000000000000000000000000000000000000000000000000000000000000003"),
1316                b256!("0x0000000000000000000000000000000000000000000000000000000000000004"),
1317                b256!("0x0000000000000000000000000000000000000000000000000000000000000005"),
1318            ]
1319            .into_iter()
1320            .collect::<HashSet<_>>(),
1321        );
1322
1323        let _ = hashes.retain_count(6);
1324
1325        assert_eq!(5, hashes.len());
1326    }
1327
1328    #[test]
1329    fn split_request_hashes_keep_none() {
1330        let mut hashes = RequestTxHashes::new(
1331            [
1332                b256!("0x0000000000000000000000000000000000000000000000000000000000000001"),
1333                b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
1334                b256!("0x0000000000000000000000000000000000000000000000000000000000000003"),
1335                b256!("0x0000000000000000000000000000000000000000000000000000000000000004"),
1336                b256!("0x0000000000000000000000000000000000000000000000000000000000000005"),
1337            ]
1338            .into_iter()
1339            .collect::<HashSet<_>>(),
1340        );
1341
1342        let rest = hashes.retain_count(0);
1343
1344        assert_eq!(0, hashes.len());
1345        assert_eq!(5, rest.len());
1346    }
1347
1348    fn signed_transaction() -> impl SignedTransaction {
1349        TransactionSigned::new_unhashed(
1350            Transaction::Legacy(Default::default()),
1351            Signature::new(
1352                U256::from_str(
1353                    "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
1354                )
1355                .unwrap(),
1356                U256::from_str(
1357                    "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
1358                )
1359                .unwrap(),
1360                false,
1361            ),
1362        )
1363    }
1364
1365    #[test]
1366    fn test_pooled_tx_hashes_68_push() {
1367        let tx = signed_transaction();
1368        let mut tx_hashes =
1369            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] };
1370        tx_hashes.push(&tx);
1371        assert_eq!(tx_hashes.types.len(), 1);
1372        assert_eq!(tx_hashes.sizes.len(), 1);
1373        assert_eq!(tx_hashes.hashes.len(), 1);
1374        assert_eq!(tx_hashes.types[0], tx.ty());
1375        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1376        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1377    }
1378
1379    #[test]
1380    fn test_pooled_tx_hashes_68_extend() {
1381        let tx = signed_transaction();
1382        let txs = vec![tx.clone(), tx.clone()];
1383        let mut tx_hashes =
1384            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] };
1385        tx_hashes.extend(&txs);
1386        assert_eq!(tx_hashes.types.len(), 2);
1387        assert_eq!(tx_hashes.sizes.len(), 2);
1388        assert_eq!(tx_hashes.hashes.len(), 2);
1389        assert_eq!(tx_hashes.types[0], tx.ty());
1390        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1391        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1392        assert_eq!(tx_hashes.types[1], tx.ty());
1393        assert_eq!(tx_hashes.sizes[1], tx.encode_2718_len());
1394        assert_eq!(tx_hashes.hashes[1], *tx.tx_hash());
1395    }
1396
1397    #[test]
1398    fn test_pooled_tx_hashes_68_with_transaction() {
1399        let tx = signed_transaction();
1400        let tx_hashes =
1401            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] }
1402                .with_transaction(&tx);
1403        assert_eq!(tx_hashes.types.len(), 1);
1404        assert_eq!(tx_hashes.sizes.len(), 1);
1405        assert_eq!(tx_hashes.hashes.len(), 1);
1406        assert_eq!(tx_hashes.types[0], tx.ty());
1407        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1408        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1409    }
1410
1411    #[test]
1412    fn test_pooled_tx_hashes_68_with_transactions() {
1413        let tx = signed_transaction();
1414        let txs = vec![tx.clone(), tx.clone()];
1415        let tx_hashes =
1416            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] }
1417                .with_transactions(&txs);
1418        assert_eq!(tx_hashes.types.len(), 2);
1419        assert_eq!(tx_hashes.sizes.len(), 2);
1420        assert_eq!(tx_hashes.hashes.len(), 2);
1421        assert_eq!(tx_hashes.types[0], tx.ty());
1422        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1423        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1424        assert_eq!(tx_hashes.types[1], tx.ty());
1425        assert_eq!(tx_hashes.sizes[1], tx.encode_2718_len());
1426        assert_eq!(tx_hashes.hashes[1], *tx.tx_hash());
1427    }
1428}