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