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_consensus::transaction::TxHashRef;
6use alloy_eips::eip2718::Typed2718;
7use alloy_primitives::{
8    bytes::BufMut,
9    map::{B256Map, B256Set},
10    Bytes, TxHash, B128, B256, U128,
11};
12use alloy_rlp::{
13    decode_append, Decodable, Encodable, Header, RlpDecodable, RlpDecodableWrapper, RlpEncodable,
14    RlpEncodableWrapper,
15};
16use core::{fmt::Debug, mem};
17use derive_more::{Constructor, Deref, DerefMut, From, IntoIterator};
18use reth_codecs_derive::{add_arbitrary_tests, generate_tests};
19use reth_ethereum_primitives::TransactionSigned;
20use reth_primitives_traits::{sync::OnceLock, Block, InMemorySize, SignedTransaction};
21
22/// Soft limit for the number of hashes in a
23/// [`NewPooledTransactionHashes`] broadcast message.
24///
25/// Spec'd at 4096 hashes.
26///
27/// <https://github.com/ethereum/devp2p/blob/master/caps/eth.md#newpooledtransactionhashes-0x08>
28pub const SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE: usize = 4096;
29
30/// This informs peers of new blocks that have appeared on the network.
31#[derive(
32    Clone,
33    Debug,
34    PartialEq,
35    Eq,
36    RlpEncodableWrapper,
37    RlpDecodableWrapper,
38    Default,
39    Deref,
40    DerefMut,
41    IntoIterator,
42)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
45#[add_arbitrary_tests(rlp)]
46pub struct NewBlockHashes(
47    /// New block hashes and the block number for each blockhash.
48    /// Clients should request blocks using a [`GetBlockBodies`](crate::GetBlockBodies) message.
49    pub Vec<BlockHashNumber>,
50);
51
52// === impl NewBlockHashes ===
53
54impl NewBlockHashes {
55    /// Returns the latest block in the list of blocks.
56    pub fn latest(&self) -> Option<&BlockHashNumber> {
57        self.iter().max_by_key(|b| b.number)
58    }
59}
60
61/// A block hash _and_ a block number.
62#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
65#[add_arbitrary_tests(rlp)]
66pub struct BlockHashNumber {
67    /// The block hash
68    pub hash: B256,
69    /// The block number
70    pub number: u64,
71}
72
73impl From<Vec<BlockHashNumber>> for NewBlockHashes {
74    fn from(v: Vec<BlockHashNumber>) -> Self {
75        Self(v)
76    }
77}
78
79impl From<NewBlockHashes> for Vec<BlockHashNumber> {
80    fn from(v: NewBlockHashes) -> Self {
81        v.0
82    }
83}
84
85/// A trait for block payloads transmitted through p2p.
86pub trait NewBlockPayload:
87    Encodable + Decodable + Clone + Eq + Debug + Send + Sync + Unpin + 'static
88{
89    /// The block type.
90    type Block: Block;
91
92    /// Returns a reference to the block.
93    fn block(&self) -> &Self::Block;
94}
95
96/// A new block with the current total difficulty, which includes the difficulty of the returned
97/// block.
98#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
101pub struct NewBlock<B = reth_ethereum_primitives::Block> {
102    /// A new block.
103    pub block: B,
104    /// The current total difficulty.
105    pub td: U128,
106}
107
108impl<B: Block + 'static> NewBlockPayload for NewBlock<B> {
109    type Block = B;
110
111    fn block(&self) -> &Self::Block {
112        &self.block
113    }
114}
115
116generate_tests!(#[rlp, 25] NewBlock<reth_ethereum_primitives::Block>, EthNewBlockTests);
117
118/// This informs peers of transactions that have appeared on the network and are not yet included
119/// in a block.
120#[derive(
121    Clone,
122    Debug,
123    PartialEq,
124    Eq,
125    RlpEncodableWrapper,
126    RlpDecodableWrapper,
127    Default,
128    Deref,
129    IntoIterator,
130)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
133#[add_arbitrary_tests(rlp, 10)]
134pub struct Transactions<T = TransactionSigned>(
135    /// New transactions for the peer to include in its mempool.
136    pub Vec<T>,
137);
138
139impl<T: SignedTransaction> Transactions<T> {
140    /// Returns `true` if the list of transactions contains any blob transactions.
141    pub fn has_eip4844(&self) -> bool {
142        self.iter().any(|tx| tx.is_eip4844())
143    }
144}
145
146impl<T> From<Vec<T>> for Transactions<T> {
147    fn from(txs: Vec<T>) -> Self {
148        Self(txs)
149    }
150}
151
152impl<T> From<Transactions<T>> for Vec<T> {
153    fn from(txs: Transactions<T>) -> Self {
154        txs.0
155    }
156}
157
158impl<T: Decodable + InMemorySize> Transactions<T> {
159    /// Decodes the RLP list of transactions, stopping once the cumulative
160    /// [`InMemorySize`] of decoded transactions exceeds `memory_budget` bytes.
161    /// Any remaining transactions in the payload are skipped.
162    pub fn decode_with_memory_budget(
163        buf: &mut &[u8],
164        memory_budget: usize,
165    ) -> alloy_rlp::Result<Self> {
166        decode_list_with_memory_budget(buf, memory_budget).map(Self)
167    }
168}
169
170/// Decodes an RLP list, stopping once the cumulative [`InMemorySize`] of decoded items exceeds
171/// `memory_budget` bytes. Any remaining items in the payload are skipped.
172pub fn decode_list_with_memory_budget<T: Decodable + InMemorySize>(
173    buf: &mut &[u8],
174    memory_budget: usize,
175) -> alloy_rlp::Result<Vec<T>> {
176    let header = Header::decode(buf)?;
177    if !header.list {
178        return Err(alloy_rlp::Error::UnexpectedString);
179    }
180    if buf.len() < header.payload_length {
181        return Err(alloy_rlp::Error::InputTooShort);
182    }
183
184    let (payload, rest) = buf.split_at(header.payload_length);
185    let mut payload = payload;
186
187    let mut txs = Vec::with_capacity(estimated_transaction_list_capacity(header.payload_length));
188    let mut total_size = 0usize;
189
190    while !payload.is_empty() {
191        let item = T::decode(&mut payload)?;
192        total_size = total_size.saturating_add(item.size());
193
194        if total_size > memory_budget {
195            break;
196        }
197
198        txs.push(item);
199    }
200
201    *buf = rest;
202    Ok(txs)
203}
204
205// Keep this as a conservative hint: small lists stay allocation-free until the first push, while
206// large untrusted payloads cannot force an outsized preallocation.
207const MIN_TRANSACTION_RLP_SIZE_ESTIMATE: usize = 128;
208const MIN_PREALLOCATED_TRANSACTIONS: usize = 4;
209const MAX_PREALLOCATED_TRANSACTIONS: usize = 1024;
210
211const fn estimated_transaction_list_capacity(payload_length: usize) -> usize {
212    let estimate = payload_length / MIN_TRANSACTION_RLP_SIZE_ESTIMATE;
213    if estimate < MIN_PREALLOCATED_TRANSACTIONS {
214        0
215    } else if estimate > MAX_PREALLOCATED_TRANSACTIONS {
216        MAX_PREALLOCATED_TRANSACTIONS
217    } else {
218        estimate
219    }
220}
221
222/// Same as [`Transactions`] but this is intended as egress message send from local to _many_ peers.
223///
224/// The list of transactions is constructed on per-peers basis, but the underlying transaction
225/// objects are shared.
226#[derive(
227    Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Deref, IntoIterator,
228)]
229#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
230#[add_arbitrary_tests(rlp, 20)]
231pub struct SharedTransactions<T = TransactionSigned>(
232    /// New transactions for the peer to include in its mempool.
233    pub Vec<Arc<T>>,
234);
235
236/// A transaction that can be lazily encoded for pool-backed outbound propagation.
237pub trait BroadcastPoolTransaction:
238    Encodable + TxHashRef + Typed2718 + Send + Sync + 'static
239{
240}
241
242impl<T> BroadcastPoolTransaction for T where
243    T: Encodable + TxHashRef + Typed2718 + Send + Sync + 'static
244{
245}
246
247/// Shared cached encoding for an outbound transaction.
248///
249/// This keeps the transaction object and its encoded bytes behind shared references so cloned
250/// per-peer messages reuse the same EIP-2718 encoding.
251pub struct LazyEncoded<T: ?Sized> {
252    value: Arc<T>,
253    encoded: Arc<OnceLock<Bytes>>,
254}
255
256impl<T: ?Sized> Clone for LazyEncoded<T> {
257    fn clone(&self) -> Self {
258        Self { value: Arc::clone(&self.value), encoded: Arc::clone(&self.encoded) }
259    }
260}
261
262impl LazyEncoded<dyn BroadcastPoolTransaction> {
263    /// Wraps a transaction-like value and lazily caches its encoded bytes.
264    pub fn new<T>(value: T) -> Self
265    where
266        T: BroadcastPoolTransaction,
267    {
268        let value: Arc<dyn BroadcastPoolTransaction> = Arc::new(value);
269        Self { value, encoded: Arc::new(OnceLock::new()) }
270    }
271}
272
273impl<T: Encodable + ?Sized> Encodable for LazyEncoded<T> {
274    fn encode(&self, out: &mut dyn BufMut) {
275        let encoded = self.encoded.get_or_init(|| self.encode_uncached());
276        out.put_slice(encoded);
277    }
278
279    fn length(&self) -> usize {
280        self.encoded.get_or_init(|| self.encode_uncached()).len()
281    }
282}
283
284impl<T: Encodable + ?Sized> LazyEncoded<T> {
285    fn encode_uncached(&self) -> Bytes {
286        let mut out = Vec::with_capacity(self.value.length());
287        self.value.encode(&mut out);
288        out.into()
289    }
290}
291
292impl<T: TxHashRef + ?Sized> TxHashRef for LazyEncoded<T> {
293    fn tx_hash(&self) -> &TxHash {
294        self.value.tx_hash()
295    }
296}
297
298impl<T: Typed2718 + ?Sized> Typed2718 for LazyEncoded<T> {
299    fn ty(&self) -> u8 {
300        self.value.ty()
301    }
302}
303
304impl<T: ?Sized> Debug for LazyEncoded<T> {
305    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
306        f.debug_struct("LazyEncoded")
307            .field("is_cached", &self.encoded.get().is_some())
308            .finish_non_exhaustive()
309    }
310}
311
312/// A lazily encoded transaction used for pool-backed full transaction propagation.
313pub type LazyEncodedTransaction = LazyEncoded<dyn BroadcastPoolTransaction>;
314
315/// Outbound-only full transaction propagation message backed by pool transactions.
316///
317/// This encodes to the same `Transactions` wire payload as [`SharedTransactions`], but is used by
318/// the transaction manager when the source is the pool. Unlike [`SharedTransactions`], it can wrap
319/// pool transaction references directly and cache each transaction's encoded bytes across per-peer
320/// messages. Queued messages retain the pool-backed value and the shared cached bytes until they
321/// are sent.
322#[derive(Clone, Debug, Deref)]
323pub struct BroadcastPoolTransactions(pub Vec<LazyEncodedTransaction>);
324
325impl BroadcastPoolTransactions {
326    /// Returns an iterator over the transaction hashes.
327    pub fn iter_hashes(&self) -> impl Iterator<Item = &TxHash> + '_ {
328        self.0.iter().map(TxHashRef::tx_hash)
329    }
330}
331
332impl Encodable for BroadcastPoolTransactions {
333    fn encode(&self, out: &mut dyn BufMut) {
334        self.0.encode(out);
335    }
336
337    fn length(&self) -> usize {
338        self.0.length()
339    }
340}
341
342/// A wrapper type for all different new pooled transaction types
343#[derive(Clone, Debug, PartialEq, Eq)]
344#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
345pub enum NewPooledTransactionHashes {
346    /// A list of transaction hashes valid for [66-68)
347    Eth66(NewPooledTransactionHashes66),
348    /// A list of transaction hashes valid for [68-72)
349    ///
350    /// Note: it is assumed that the payload is valid (all vectors have the same length)
351    Eth68(NewPooledTransactionHashes68),
352    /// A list of transaction hashes valid from [72..]
353    ///
354    /// This extends the eth/68 announcement payload with the `cell_mask` field introduced by
355    /// [EIP-8070](https://eips.ethereum.org/EIPS/eip-8070).
356    ///
357    /// Note: it is assumed that the payload is valid (all vectors have the same length)
358    Eth72(NewPooledTransactionHashes72),
359}
360
361// === impl NewPooledTransactionHashes ===
362
363impl NewPooledTransactionHashes {
364    /// Returns the message [`EthVersion`].
365    pub const fn version(&self) -> EthVersion {
366        match self {
367            Self::Eth66(_) => EthVersion::Eth66,
368            Self::Eth68(_) => EthVersion::Eth68,
369            Self::Eth72(_) => EthVersion::Eth72,
370        }
371    }
372
373    /// Returns `true` if the payload is valid for the given version
374    pub const fn is_valid_for_version(&self, version: EthVersion) -> bool {
375        match self {
376            Self::Eth66(_) => {
377                matches!(version, EthVersion::Eth67 | EthVersion::Eth66)
378            }
379            Self::Eth68(_) => {
380                matches!(
381                    version,
382                    EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71
383                )
384            }
385            Self::Eth72(_) => {
386                matches!(version, EthVersion::Eth72)
387            }
388        }
389    }
390
391    /// Returns an iterator over all transaction hashes.
392    pub fn iter_hashes(&self) -> impl Iterator<Item = &B256> + '_ {
393        match self {
394            Self::Eth66(msg) => msg.iter(),
395            Self::Eth68(msg) => msg.hashes.iter(),
396            Self::Eth72(msg) => msg.hashes.iter(),
397        }
398    }
399
400    /// Returns an immutable reference to transaction hashes.
401    pub const fn hashes(&self) -> &Vec<B256> {
402        match self {
403            Self::Eth66(msg) => &msg.0,
404            Self::Eth68(msg) => &msg.hashes,
405            Self::Eth72(msg) => &msg.hashes,
406        }
407    }
408
409    /// Returns a mutable reference to transaction hashes.
410    pub const fn hashes_mut(&mut self) -> &mut Vec<B256> {
411        match self {
412            Self::Eth66(msg) => &mut msg.0,
413            Self::Eth68(msg) => &mut msg.hashes,
414            Self::Eth72(msg) => &mut msg.hashes,
415        }
416    }
417
418    /// Consumes the type and returns all hashes
419    pub fn into_hashes(self) -> Vec<B256> {
420        match self {
421            Self::Eth66(msg) => msg.0,
422            Self::Eth68(msg) => msg.hashes,
423            Self::Eth72(msg) => msg.hashes,
424        }
425    }
426
427    /// Returns an iterator over all transaction hashes.
428    pub fn into_iter_hashes(self) -> impl Iterator<Item = B256> {
429        match self {
430            Self::Eth66(msg) => msg.into_iter(),
431            Self::Eth68(msg) => msg.hashes.into_iter(),
432            Self::Eth72(msg) => msg.hashes.into_iter(),
433        }
434    }
435
436    /// Shortens the number of hashes in the message, keeping the first `len` hashes and dropping
437    /// the rest. If `len` is greater than the number of hashes, this has no effect.
438    pub fn truncate(&mut self, len: usize) {
439        match self {
440            Self::Eth66(msg) => msg.truncate(len),
441            Self::Eth68(msg) => {
442                msg.types.truncate(len);
443                msg.sizes.truncate(len);
444                msg.hashes.truncate(len);
445            }
446            Self::Eth72(msg) => {
447                msg.types.truncate(len);
448                msg.sizes.truncate(len);
449                msg.hashes.truncate(len);
450            }
451        }
452    }
453
454    /// Returns true if the message is empty
455    pub const fn is_empty(&self) -> bool {
456        match self {
457            Self::Eth66(msg) => msg.0.is_empty(),
458            Self::Eth68(msg) => msg.hashes.is_empty(),
459            Self::Eth72(msg) => msg.hashes.is_empty(),
460        }
461    }
462
463    /// Returns the number of hashes in the message
464    pub const fn len(&self) -> usize {
465        match self {
466            Self::Eth66(msg) => msg.0.len(),
467            Self::Eth68(msg) => msg.hashes.len(),
468            Self::Eth72(msg) => msg.hashes.len(),
469        }
470    }
471
472    /// Returns an immutable reference to the inner type if this is an eth68 announcement.
473    pub const fn as_eth72(&self) -> Option<&NewPooledTransactionHashes72> {
474        match self {
475            Self::Eth66(_) | Self::Eth68(_) => None,
476            Self::Eth72(msg) => Some(msg),
477        }
478    }
479
480    /// Returns a mutable reference to the inner type if this is an eth68 announcement.
481    pub const fn as_eth72_mut(&mut self) -> Option<&mut NewPooledTransactionHashes72> {
482        match self {
483            Self::Eth66(_) | Self::Eth68(_) => None,
484            Self::Eth72(msg) => Some(msg),
485        }
486    }
487
488    /// Returns an immutable reference to the inner type if this is an eth68 announcement.
489    pub const fn as_eth68(&self) -> Option<&NewPooledTransactionHashes68> {
490        match self {
491            Self::Eth66(_) | Self::Eth72(_) => None,
492            Self::Eth68(msg) => Some(msg),
493        }
494    }
495
496    /// Returns a mutable reference to the inner type if this is an eth68 announcement.
497    pub const fn as_eth68_mut(&mut self) -> Option<&mut NewPooledTransactionHashes68> {
498        match self {
499            Self::Eth66(_) | Self::Eth72(_) => None,
500            Self::Eth68(msg) => Some(msg),
501        }
502    }
503
504    /// Returns a mutable reference to the inner type if this is an eth66 announcement.
505    pub const fn as_eth66_mut(&mut self) -> Option<&mut NewPooledTransactionHashes66> {
506        match self {
507            Self::Eth66(msg) => Some(msg),
508            Self::Eth68(_) | Self::Eth72(_) => None,
509        }
510    }
511
512    /// Returns the inner type if this is an eth68 announcement.
513    pub fn take_eth68(&mut self) -> Option<NewPooledTransactionHashes68> {
514        match self {
515            Self::Eth66(_) | Self::Eth72(_) => None,
516            Self::Eth68(msg) => Some(mem::take(msg)),
517        }
518    }
519
520    /// Returns the inner type if this is an eth66 announcement.
521    pub fn take_eth66(&mut self) -> Option<NewPooledTransactionHashes66> {
522        match self {
523            Self::Eth66(msg) => Some(mem::take(msg)),
524            Self::Eth68(_) | Self::Eth72(_) => None,
525        }
526    }
527}
528
529impl<N: NetworkPrimitives> From<NewPooledTransactionHashes> for EthMessage<N> {
530    fn from(value: NewPooledTransactionHashes) -> Self {
531        match value {
532            NewPooledTransactionHashes::Eth66(msg) => Self::NewPooledTransactionHashes66(msg),
533            NewPooledTransactionHashes::Eth68(msg) => Self::NewPooledTransactionHashes68(msg),
534            NewPooledTransactionHashes::Eth72(msg) => Self::NewPooledTransactionHashes72(msg),
535        }
536    }
537}
538
539impl From<NewPooledTransactionHashes66> for NewPooledTransactionHashes {
540    fn from(hashes: NewPooledTransactionHashes66) -> Self {
541        Self::Eth66(hashes)
542    }
543}
544
545impl From<NewPooledTransactionHashes68> for NewPooledTransactionHashes {
546    fn from(hashes: NewPooledTransactionHashes68) -> Self {
547        Self::Eth68(hashes)
548    }
549}
550
551impl From<NewPooledTransactionHashes72> for NewPooledTransactionHashes {
552    fn from(hashes: NewPooledTransactionHashes72) -> Self {
553        Self::Eth72(hashes)
554    }
555}
556
557/// This informs peers of transaction hashes for transactions that have appeared on the network,
558/// but have not been included in a block.
559#[derive(
560    Clone,
561    Debug,
562    PartialEq,
563    Eq,
564    RlpEncodableWrapper,
565    RlpDecodableWrapper,
566    Default,
567    Deref,
568    DerefMut,
569    IntoIterator,
570)]
571#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
572#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
573#[add_arbitrary_tests(rlp)]
574pub struct NewPooledTransactionHashes66(
575    /// Transaction hashes for new transactions that have appeared on the network.
576    /// Clients should request the transactions with the given hashes using a
577    /// [`GetPooledTransactions`](crate::GetPooledTransactions) message.
578    pub Vec<B256>,
579);
580
581impl NewPooledTransactionHashes66 {
582    /// Returns a new instance with capacity for `capacity` hashes.
583    pub fn with_capacity(capacity: usize) -> Self {
584        Self(Vec::with_capacity(capacity))
585    }
586}
587
588impl From<Vec<B256>> for NewPooledTransactionHashes66 {
589    fn from(v: Vec<B256>) -> Self {
590        Self(v)
591    }
592}
593
594/// Same as [`NewPooledTransactionHashes66`] but extends that beside the transaction hashes,
595/// the node sends the transaction types and their sizes (as defined in EIP-2718) as well.
596#[derive(Clone, Debug, PartialEq, Eq, Default)]
597#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
598pub struct NewPooledTransactionHashes68 {
599    /// Transaction types for new transactions that have appeared on the network.
600    ///
601    /// ## Note on RLP encoding and decoding
602    ///
603    /// In the [eth/68 spec](https://eips.ethereum.org/EIPS/eip-5793#specification) this is defined
604    /// the following way:
605    ///  * `[type_0: B_1, type_1: B_1, ...]`
606    ///
607    /// This would make it seem like the [`Encodable`] and
608    /// [`Decodable`] implementations should directly use a `Vec<u8>` for
609    /// encoding and decoding, because it looks like this field should be encoded as a _list_ of
610    /// bytes.
611    ///
612    /// However, [this is implemented in geth as a `[]byte`
613    /// type](https://github.com/ethereum/go-ethereum/blob/82d934b1dd80cdd8190803ea9f73ed2c345e2576/eth/protocols/eth/protocol.go#L308-L313),
614    /// which [ends up being encoded as a RLP
615    /// string](https://github.com/ethereum/go-ethereum/blob/82d934b1dd80cdd8190803ea9f73ed2c345e2576/rlp/encode_test.go#L171-L176),
616    /// **not** a RLP list.
617    ///
618    /// Because of this, we do not directly use the `Vec<u8>` when encoding and decoding, and
619    /// instead use the [`Encodable`] and [`Decodable`]
620    /// implementations for `&[u8]` instead, which encodes into a RLP string, and expects an RLP
621    /// string when decoding.
622    pub types: Vec<u8>,
623    /// Transaction sizes for new transactions that have appeared on the network.
624    pub sizes: Vec<usize>,
625    /// Transaction hashes for new transactions that have appeared on the network.
626    pub hashes: Vec<B256>,
627}
628
629#[cfg(feature = "arbitrary")]
630impl proptest::prelude::Arbitrary for NewPooledTransactionHashes68 {
631    type Parameters = ();
632    fn arbitrary_with(_args: ()) -> Self::Strategy {
633        use proptest::{collection::vec, prelude::*};
634        // Generate a single random length for all vectors
635        let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); // Lengths between 1 and 100
636
637        vec_length
638            .prop_flat_map(|len| {
639                // Use the generated length to create vectors of TxType, usize, and B256
640                let types_vec = vec(
641                    proptest_arbitrary_interop::arb::<reth_ethereum_primitives::TxType>()
642                        .prop_map(|ty| ty as u8),
643                    len..=len,
644                );
645
646                // Map the usize values to the range 0..131072(0x20000)
647                let sizes_vec = vec(proptest::num::usize::ANY.prop_map(|x| x % 131072), len..=len);
648                let hashes_vec = vec(any::<B256>(), len..=len);
649
650                (types_vec, sizes_vec, hashes_vec)
651            })
652            .prop_map(|(types, sizes, hashes)| Self { types, sizes, hashes })
653            .boxed()
654    }
655
656    type Strategy = proptest::prelude::BoxedStrategy<Self>;
657}
658
659impl NewPooledTransactionHashes68 {
660    /// Returns a new instance with capacity for `capacity` entries.
661    pub fn with_capacity(capacity: usize) -> Self {
662        Self {
663            types: Vec::with_capacity(capacity),
664            sizes: Vec::with_capacity(capacity),
665            hashes: Vec::with_capacity(capacity),
666        }
667    }
668
669    /// Returns an iterator over tx hashes zipped with corresponding metadata.
670    pub fn metadata_iter(&self) -> impl Iterator<Item = (&B256, (u8, usize))> {
671        self.hashes.iter().zip(self.types.iter().copied().zip(self.sizes.iter().copied()))
672    }
673
674    /// Appends a transaction
675    pub fn push<T: SignedTransaction>(&mut self, tx: &T) {
676        self.hashes.push(*tx.tx_hash());
677        self.sizes.push(tx.encode_2718_len());
678        self.types.push(tx.ty());
679    }
680
681    /// Appends the provided transactions
682    pub fn extend<'a, T: SignedTransaction>(&mut self, txs: impl IntoIterator<Item = &'a T>) {
683        for tx in txs {
684            self.push(tx);
685        }
686    }
687
688    /// Shrinks the capacity of the message vectors as much as possible.
689    pub fn shrink_to_fit(&mut self) {
690        self.hashes.shrink_to_fit();
691        self.sizes.shrink_to_fit();
692        self.types.shrink_to_fit()
693    }
694
695    /// Consumes and appends a transaction
696    pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
697        self.push(tx);
698        self
699    }
700
701    /// Consumes and appends the provided transactions
702    pub fn with_transactions<'a, T: SignedTransaction>(
703        mut self,
704        txs: impl IntoIterator<Item = &'a T>,
705    ) -> Self {
706        self.extend(txs);
707        self
708    }
709}
710
711impl Encodable for NewPooledTransactionHashes68 {
712    fn encode(&self, out: &mut dyn bytes::BufMut) {
713        #[derive(RlpEncodable)]
714        struct EncodableNewPooledTransactionHashes68<'a> {
715            types: &'a [u8],
716            sizes: &'a Vec<usize>,
717            hashes: &'a Vec<B256>,
718        }
719
720        let encodable = EncodableNewPooledTransactionHashes68 {
721            types: &self.types[..],
722            sizes: &self.sizes,
723            hashes: &self.hashes,
724        };
725
726        encodable.encode(out);
727    }
728    fn length(&self) -> usize {
729        #[derive(RlpEncodable)]
730        struct EncodableNewPooledTransactionHashes68<'a> {
731            types: &'a [u8],
732            sizes: &'a Vec<usize>,
733            hashes: &'a Vec<B256>,
734        }
735
736        let encodable = EncodableNewPooledTransactionHashes68 {
737            types: &self.types[..],
738            sizes: &self.sizes,
739            hashes: &self.hashes,
740        };
741
742        encodable.length()
743    }
744}
745
746impl Decodable for NewPooledTransactionHashes68 {
747    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
748        let Header { list, payload_length } = Header::decode(buf)?;
749        if !list {
750            return Err(alloy_rlp::Error::UnexpectedString)
751        }
752        if buf.len() < payload_length {
753            return Err(alloy_rlp::Error::InputTooShort)
754        }
755
756        let (mut payload, rest) = buf.split_at(payload_length);
757        let (types, sizes, hashes) = decode_pooled_transaction_hashes_payload(&mut payload)?;
758
759        if !payload.is_empty() {
760            return Err(alloy_rlp::Error::ListLengthMismatch {
761                expected: payload_length,
762                got: payload_length - payload.len(),
763            })
764        }
765
766        ensure_pooled_transaction_hashes_lengths(hashes.len(), types.len(), sizes.len())?;
767
768        let msg = Self { types, sizes, hashes };
769
770        *buf = rest;
771        Ok(msg)
772    }
773}
774
775/// Same as [`NewPooledTransactionHashes68`] but adds the eth/72 `cell_mask` field from
776/// [EIP-8070](https://eips.ethereum.org/EIPS/eip-8070).
777#[derive(Clone, Debug, PartialEq, Eq, Default)]
778#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
779pub struct NewPooledTransactionHashes72 {
780    /// Transaction types for new transactions that have appeared on the network.
781    ///
782    /// ## Note on RLP encoding and decoding
783    ///
784    /// In the [eth/72 spec](https://eips.ethereum.org/EIPS/eip-8070#specification) this is defined
785    /// the following way:
786    ///  * `[types: B, [size_0: P, size_1: P, ...], [hash_0: B_32, hash_1: B_32, ...], cell_mask:
787    ///    B_16]`
788    pub types: Vec<u8>,
789    /// Transaction sizes for new transactions that have appeared on the network.
790    pub sizes: Vec<usize>,
791    /// Transaction hashes for new transactions that have appeared on the network.
792    pub hashes: Vec<B256>,
793    /// Cell availability mask for type 3 (blob) transactions announced by this message.
794    ///
795    /// Per [EIP-8070](https://eips.ethereum.org/EIPS/eip-8070), this is a `B_16`
796    /// bitarray over `CELLS_PER_EXT_BLOB`; bit `i` is set when the announcer has column
797    /// `i` available for every type 3 transaction in the message.
798    ///
799    /// On the wire this field is always encoded as a 16 byte string, zero-filled when no
800    /// type 3 transactions are announced: go-ethereum decodes the mask into a fixed
801    /// `[16]byte` and rejects the RLP `nil` encoding the EIP text describes, so the
802    /// always-present form is the de facto network format. `None` is equivalent to a zero
803    /// mask; decoding additionally accepts the spec's `nil` encoding for compatibility.
804    pub cell_mask: Option<B128>,
805}
806
807#[cfg(feature = "arbitrary")]
808impl proptest::prelude::Arbitrary for NewPooledTransactionHashes72 {
809    type Parameters = ();
810    fn arbitrary_with(_args: ()) -> Self::Strategy {
811        use proptest::{collection::vec, prelude::*};
812        // Generate a single random length for all vectors
813        let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); // Lengths between 1 and 100
814
815        vec_length
816            .prop_flat_map(|len| {
817                // Use the generated length to create vectors of TxType, usize, and B256
818                let types_vec = vec(
819                    proptest_arbitrary_interop::arb::<reth_ethereum_primitives::TxType>()
820                        .prop_map(|ty| ty as u8),
821                    len..=len,
822                );
823
824                // Map the usize values to the range 0..131072(0x20000)
825                let sizes_vec = vec(proptest::num::usize::ANY.prop_map(|x| x % 131072), len..=len);
826                let hashes_vec = vec(any::<B256>(), len..=len);
827                let cell_mask = any::<Option<B128>>();
828
829                (types_vec, sizes_vec, hashes_vec, cell_mask)
830            })
831            .prop_map(|(types, sizes, hashes, cell_mask)| Self { types, sizes, hashes, cell_mask })
832            .boxed()
833    }
834
835    type Strategy = proptest::prelude::BoxedStrategy<Self>;
836}
837
838impl NewPooledTransactionHashes72 {
839    /// Cell mask advertising availability of every cell.
840    ///
841    /// Used when announcing blob transactions whose full sidecar is available locally, since
842    /// every cell can be computed from the complete blob data.
843    pub const ALL_CELLS_MASK: B128 = B128::repeat_byte(0xff);
844
845    /// Returns a new instance with capacity for `capacity` entries and no cell mask.
846    pub fn with_capacity(capacity: usize) -> Self {
847        Self {
848            types: Vec::with_capacity(capacity),
849            sizes: Vec::with_capacity(capacity),
850            hashes: Vec::with_capacity(capacity),
851            cell_mask: None,
852        }
853    }
854
855    /// Returns an iterator over tx hashes zipped with corresponding metadata.
856    pub fn metadata_iter(&self) -> impl Iterator<Item = (&B256, (u8, usize))> {
857        self.hashes.iter().zip(self.types.iter().copied().zip(self.sizes.iter().copied()))
858    }
859
860    /// Appends a transaction
861    pub fn push<T: SignedTransaction>(&mut self, tx: &T) {
862        self.hashes.push(*tx.tx_hash());
863        self.sizes.push(tx.encode_2718_len());
864        self.types.push(tx.ty());
865        if tx.is_eip4844() {
866            self.cell_mask = Some(Self::ALL_CELLS_MASK);
867        }
868    }
869
870    /// Appends the provided transactions
871    pub fn extend<'a, T: SignedTransaction>(&mut self, txs: impl IntoIterator<Item = &'a T>) {
872        for tx in txs {
873            self.push(tx);
874        }
875    }
876
877    /// Shrinks the capacity of the message vectors as much as possible.
878    pub fn shrink_to_fit(&mut self) {
879        self.hashes.shrink_to_fit();
880        self.sizes.shrink_to_fit();
881        self.types.shrink_to_fit()
882    }
883
884    /// Consumes and appends a transaction
885    pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
886        self.push(tx);
887        self
888    }
889
890    /// Consumes and appends the provided transactions
891    pub fn with_transactions<'a, T: SignedTransaction>(
892        mut self,
893        txs: impl IntoIterator<Item = &'a T>,
894    ) -> Self {
895        self.extend(txs);
896        self
897    }
898
899    fn payload_length(&self) -> usize {
900        self.types.as_slice().length() +
901            self.sizes.length() +
902            self.hashes.length() +
903            self.cell_mask.unwrap_or_default().length()
904    }
905}
906
907impl Encodable for NewPooledTransactionHashes72 {
908    fn encode(&self, out: &mut dyn bytes::BufMut) {
909        Header { list: true, payload_length: self.payload_length() }.encode(out);
910        self.types.as_slice().encode(out);
911        self.sizes.encode(out);
912        self.hashes.encode(out);
913        // A zero-filled mask when no cells are available, see the `cell_mask` field docs.
914        self.cell_mask.unwrap_or_default().encode(out);
915    }
916
917    fn length(&self) -> usize {
918        Header { list: true, payload_length: self.payload_length() }.length_with_payload()
919    }
920}
921
922impl Decodable for NewPooledTransactionHashes72 {
923    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
924        let Header { list, payload_length } = Header::decode(buf)?;
925        if !list {
926            return Err(alloy_rlp::Error::UnexpectedString)
927        }
928        if buf.len() < payload_length {
929            return Err(alloy_rlp::Error::InputTooShort)
930        }
931
932        let (mut payload, rest) = buf.split_at(payload_length);
933        let (types, sizes, hashes) = decode_pooled_transaction_hashes_payload(&mut payload)?;
934        let Some(first_byte) = payload.first().copied() else {
935            return Err(alloy_rlp::Error::InputTooShort)
936        };
937        let cell_mask = if first_byte == alloy_rlp::EMPTY_STRING_CODE {
938            // The EIP-8070 `nil` encoding, tolerated for compatibility.
939            payload = &payload[1..];
940            None
941        } else {
942            // A zero mask is the wire representation of "no cells available", see the
943            // `cell_mask` field docs.
944            Some(B128::decode(&mut payload)?).filter(|mask| !mask.is_zero())
945        };
946
947        if !payload.is_empty() {
948            return Err(alloy_rlp::Error::ListLengthMismatch {
949                expected: payload_length,
950                got: payload_length - payload.len(),
951            })
952        }
953
954        ensure_pooled_transaction_hashes_lengths(hashes.len(), types.len(), sizes.len())?;
955
956        *buf = rest;
957
958        Ok(Self { types, sizes, hashes, cell_mask })
959    }
960}
961
962/// Twice the spec'd soft limit for `NewPooledTransactionHashes` announcements.
963///
964/// This keeps capacity hints bounded when a malformed packet spends most of its bytes on the
965/// one-byte `types` string before the size and hash lists are validated.
966const NEW_POOLED_TRANSACTION_HASHES_DECODE_CAP: usize =
967    2 * SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE;
968
969#[inline]
970fn decode_pooled_transaction_hashes_payload(
971    payload: &mut &[u8],
972) -> alloy_rlp::Result<(Vec<u8>, Vec<usize>, Vec<B256>)> {
973    let types = Bytes::decode(payload)?;
974    let capacity = types.len().min(NEW_POOLED_TRANSACTION_HASHES_DECODE_CAP);
975
976    let mut sizes = Vec::with_capacity(capacity);
977    decode_append(payload, &mut sizes)?;
978
979    let mut hashes = Vec::with_capacity(capacity);
980    decode_append(payload, &mut hashes)?;
981
982    Ok((types.into(), sizes, hashes))
983}
984
985#[inline]
986const fn ensure_pooled_transaction_hashes_lengths(
987    hashes_len: usize,
988    types_len: usize,
989    sizes_len: usize,
990) -> alloy_rlp::Result<()> {
991    if hashes_len != types_len {
992        return Err(alloy_rlp::Error::ListLengthMismatch { expected: hashes_len, got: types_len })
993    }
994    if hashes_len != sizes_len {
995        return Err(alloy_rlp::Error::ListLengthMismatch { expected: hashes_len, got: sizes_len })
996    }
997
998    Ok(())
999}
1000
1001/// Validation pass that checks for unique transaction hashes.
1002pub trait DedupPayload {
1003    /// Value type in [`PartiallyValidData`] map.
1004    type Value;
1005
1006    /// The payload contains no entries.
1007    fn is_empty(&self) -> bool;
1008
1009    /// Returns the number of entries.
1010    fn len(&self) -> usize;
1011
1012    /// Consumes self, returning an iterator over hashes in payload.
1013    fn dedup(self) -> PartiallyValidData<Self::Value>;
1014}
1015
1016/// Value in [`PartiallyValidData`] map obtained from an announcement.
1017pub type Eth68TxMetadata = Option<(u8, usize)>;
1018
1019impl DedupPayload for NewPooledTransactionHashes {
1020    type Value = Eth68TxMetadata;
1021
1022    fn is_empty(&self) -> bool {
1023        self.is_empty()
1024    }
1025
1026    fn len(&self) -> usize {
1027        self.len()
1028    }
1029
1030    fn dedup(self) -> PartiallyValidData<Self::Value> {
1031        match self {
1032            Self::Eth66(msg) => msg.dedup(),
1033            Self::Eth68(msg) => msg.dedup(),
1034            Self::Eth72(msg) => msg.dedup(),
1035        }
1036    }
1037}
1038
1039impl DedupPayload for NewPooledTransactionHashes72 {
1040    type Value = Eth68TxMetadata;
1041
1042    fn is_empty(&self) -> bool {
1043        self.hashes.is_empty()
1044    }
1045
1046    fn len(&self) -> usize {
1047        self.hashes.len()
1048    }
1049
1050    fn dedup(self) -> PartiallyValidData<Self::Value> {
1051        let Self { hashes, mut sizes, mut types, cell_mask } = self;
1052
1053        let mut deduped_data = B256Map::with_capacity_and_hasher(hashes.len(), Default::default());
1054
1055        for hash in hashes.into_iter().rev() {
1056            if let (Some(ty), Some(size)) = (types.pop(), sizes.pop()) {
1057                deduped_data.insert(hash, Some((ty, size)));
1058            }
1059        }
1060
1061        PartiallyValidData::from_raw_data_eth72_with_cell_mask(deduped_data, cell_mask)
1062    }
1063}
1064
1065impl DedupPayload for NewPooledTransactionHashes68 {
1066    type Value = Eth68TxMetadata;
1067
1068    fn is_empty(&self) -> bool {
1069        self.hashes.is_empty()
1070    }
1071
1072    fn len(&self) -> usize {
1073        self.hashes.len()
1074    }
1075
1076    fn dedup(self) -> PartiallyValidData<Self::Value> {
1077        let Self { hashes, mut sizes, mut types } = self;
1078
1079        let mut deduped_data = B256Map::with_capacity_and_hasher(hashes.len(), Default::default());
1080
1081        for hash in hashes.into_iter().rev() {
1082            if let (Some(ty), Some(size)) = (types.pop(), sizes.pop()) {
1083                deduped_data.insert(hash, Some((ty, size)));
1084            }
1085        }
1086
1087        PartiallyValidData::from_raw_data_eth68(deduped_data)
1088    }
1089}
1090
1091impl DedupPayload for NewPooledTransactionHashes66 {
1092    type Value = Eth68TxMetadata;
1093
1094    fn is_empty(&self) -> bool {
1095        self.0.is_empty()
1096    }
1097
1098    fn len(&self) -> usize {
1099        self.0.len()
1100    }
1101
1102    fn dedup(self) -> PartiallyValidData<Self::Value> {
1103        let Self(hashes) = self;
1104
1105        let mut deduped_data = B256Map::with_capacity_and_hasher(hashes.len(), Default::default());
1106
1107        let noop_value: Eth68TxMetadata = None;
1108
1109        for hash in hashes.into_iter().rev() {
1110            deduped_data.insert(hash, noop_value);
1111        }
1112
1113        PartiallyValidData::from_raw_data_eth66(deduped_data)
1114    }
1115}
1116
1117/// Interface for handling mempool message data. Used in various filters in pipelines in
1118/// `TransactionsManager` and in queries to `TransactionPool`.
1119pub trait HandleMempoolData {
1120    /// The announcement contains no entries.
1121    fn is_empty(&self) -> bool;
1122
1123    /// Returns the number of entries.
1124    fn len(&self) -> usize;
1125
1126    /// Retain only entries for which the hash in the entry satisfies a given predicate.
1127    fn retain_by_hash(&mut self, f: impl FnMut(&TxHash) -> bool);
1128}
1129
1130/// Extension of [`HandleMempoolData`] interface, for mempool messages that are versioned.
1131pub trait HandleVersionedMempoolData {
1132    /// Returns the announcement version, either [`Eth66`](EthVersion::Eth66) or
1133    /// [`Eth68`](EthVersion::Eth68).
1134    fn msg_version(&self) -> EthVersion;
1135}
1136
1137impl<T: SignedTransaction> HandleMempoolData for Vec<T> {
1138    fn is_empty(&self) -> bool {
1139        self.is_empty()
1140    }
1141
1142    fn len(&self) -> usize {
1143        self.len()
1144    }
1145
1146    fn retain_by_hash(&mut self, mut f: impl FnMut(&TxHash) -> bool) {
1147        self.retain(|tx| f(tx.tx_hash()))
1148    }
1149}
1150
1151macro_rules! handle_mempool_data_map_impl {
1152    ($data_ty:ty, $(<$generic:ident>)?) => {
1153        impl$(<$generic>)? HandleMempoolData for $data_ty {
1154            fn is_empty(&self) -> bool {
1155                self.data.is_empty()
1156            }
1157
1158            fn len(&self) -> usize {
1159                self.data.len()
1160            }
1161
1162            fn retain_by_hash(&mut self, mut f: impl FnMut(&TxHash) -> bool) {
1163                self.data.retain(|hash, _| f(hash));
1164            }
1165        }
1166    };
1167}
1168
1169/// Data that has passed an initial validation pass that is not specific to any mempool message
1170/// type.
1171#[derive(Debug, Deref, DerefMut, IntoIterator)]
1172pub struct PartiallyValidData<V> {
1173    #[deref]
1174    #[deref_mut]
1175    #[into_iterator]
1176    data: B256Map<V>,
1177    version: Option<EthVersion>,
1178    /// The eth/72 message-level cell mask, if present.
1179    cell_mask: Option<B128>,
1180}
1181
1182handle_mempool_data_map_impl!(PartiallyValidData<V>, <V>);
1183
1184impl<V> PartiallyValidData<V> {
1185    /// Wraps raw data.
1186    pub const fn from_raw_data(data: B256Map<V>, version: Option<EthVersion>) -> Self {
1187        Self { data, version, cell_mask: None }
1188    }
1189
1190    /// Wraps raw data with version [`EthVersion::Eth72`].
1191    pub const fn from_raw_data_eth72(data: B256Map<V>) -> Self {
1192        Self::from_raw_data(data, Some(EthVersion::Eth72))
1193    }
1194
1195    /// Wraps raw data with an eth/72 message-level cell mask.
1196    pub const fn from_raw_data_eth72_with_cell_mask(
1197        data: B256Map<V>,
1198        cell_mask: Option<B128>,
1199    ) -> Self {
1200        Self { data, version: Some(EthVersion::Eth72), cell_mask }
1201    }
1202
1203    /// Wraps raw data with version [`EthVersion::Eth68`].
1204    pub const fn from_raw_data_eth68(data: B256Map<V>) -> Self {
1205        Self::from_raw_data(data, Some(EthVersion::Eth68))
1206    }
1207
1208    /// Wraps raw data with version [`EthVersion::Eth66`].
1209    pub const fn from_raw_data_eth66(data: B256Map<V>) -> Self {
1210        Self::from_raw_data(data, Some(EthVersion::Eth66))
1211    }
1212
1213    /// Returns a new [`PartiallyValidData`] with empty data from an [`Eth72`](EthVersion::Eth72)
1214    /// announcement.
1215    pub fn empty_eth72() -> Self {
1216        Self::from_raw_data_eth72(B256Map::default())
1217    }
1218
1219    /// Returns a new [`PartiallyValidData`] with empty data from an [`Eth68`](EthVersion::Eth68)
1220    /// announcement.
1221    pub fn empty_eth68() -> Self {
1222        Self::from_raw_data_eth68(B256Map::default())
1223    }
1224
1225    /// Returns a new [`PartiallyValidData`] with empty data from an [`Eth66`](EthVersion::Eth66)
1226    /// announcement.
1227    pub fn empty_eth66() -> Self {
1228        Self::from_raw_data_eth66(B256Map::default())
1229    }
1230
1231    /// Returns the version of the message this data was received in if different versions of the
1232    /// message exist.
1233    pub const fn msg_version(&self) -> Option<EthVersion> {
1234        self.version
1235    }
1236
1237    /// Returns the eth/72 message-level cell mask, if present.
1238    pub const fn eth72_cell_mask(&self) -> Option<B128> {
1239        self.cell_mask
1240    }
1241
1242    /// Destructs returning the validated data.
1243    pub fn into_data(self) -> B256Map<V> {
1244        self.data
1245    }
1246}
1247
1248/// Partially validated data from an announcement or a
1249/// [`PooledTransactions`](crate::PooledTransactions) response.
1250#[derive(Debug, Deref, DerefMut, IntoIterator, From)]
1251pub struct ValidAnnouncementData {
1252    #[deref]
1253    #[deref_mut]
1254    #[into_iterator]
1255    data: B256Map<Eth68TxMetadata>,
1256    version: EthVersion,
1257    /// The eth/72 message-level cell mask, if present.
1258    cell_mask: Option<B128>,
1259}
1260
1261handle_mempool_data_map_impl!(ValidAnnouncementData,);
1262
1263impl ValidAnnouncementData {
1264    /// Destructs returning only the valid hashes and the announcement message version. Caution! If
1265    /// this is [`Eth68`](EthVersion::Eth68) announcement data, this drops the metadata.
1266    pub fn into_request_hashes(self) -> (RequestTxHashes, EthVersion) {
1267        let hashes = self.data.into_keys().collect::<B256Set>();
1268
1269        (RequestTxHashes::new(hashes), self.version)
1270    }
1271
1272    /// Conversion from [`PartiallyValidData`] from an announcement. Note! [`PartiallyValidData`]
1273    /// from an announcement, should have some [`EthVersion`]. Panics if [`PartiallyValidData`] has
1274    /// version set to `None`.
1275    pub fn from_partially_valid_data(data: PartiallyValidData<Eth68TxMetadata>) -> Self {
1276        let PartiallyValidData { data, version, cell_mask } = data;
1277
1278        let version = version.expect("should have eth version for conversion");
1279
1280        Self { data, version, cell_mask }
1281    }
1282
1283    /// Returns the eth/72 message-level cell mask, if present.
1284    pub const fn eth72_cell_mask(&self) -> Option<B128> {
1285        self.cell_mask
1286    }
1287
1288    /// Destructs returning the validated data.
1289    pub fn into_data(self) -> B256Map<Eth68TxMetadata> {
1290        self.data
1291    }
1292}
1293
1294impl HandleVersionedMempoolData for ValidAnnouncementData {
1295    fn msg_version(&self) -> EthVersion {
1296        self.version
1297    }
1298}
1299
1300/// Hashes to request from a peer.
1301#[derive(Debug, Default, Deref, DerefMut, IntoIterator, Constructor)]
1302pub struct RequestTxHashes {
1303    #[deref]
1304    #[deref_mut]
1305    #[into_iterator(owned, ref)]
1306    hashes: B256Set,
1307}
1308
1309impl RequestTxHashes {
1310    /// Returns a new [`RequestTxHashes`] with given capacity for hashes. Caution! Make sure to
1311    /// call `shrink_to_fit` on [`RequestTxHashes`] when full, especially where it will
1312    /// be stored in its entirety like in the future waiting for a
1313    /// [`GetPooledTransactions`](crate::GetPooledTransactions) request to resolve.
1314    pub fn with_capacity(capacity: usize) -> Self {
1315        Self::new(B256Set::with_capacity_and_hasher(capacity, Default::default()))
1316    }
1317
1318    /// Returns a new empty instance.
1319    fn empty() -> Self {
1320        Self::new(B256Set::default())
1321    }
1322
1323    /// Retains the given number of elements, returning an iterator over the rest.
1324    pub fn retain_count(&mut self, count: usize) -> Self {
1325        let rest_capacity = self.hashes.len().saturating_sub(count);
1326        if rest_capacity == 0 {
1327            return Self::empty()
1328        }
1329        let mut rest = Self::with_capacity(rest_capacity);
1330
1331        let mut i = 0;
1332        self.hashes.retain(|hash| {
1333            if i >= count {
1334                rest.insert(*hash);
1335                return false
1336            }
1337            i += 1;
1338
1339            true
1340        });
1341
1342        rest
1343    }
1344}
1345
1346impl FromIterator<(TxHash, Eth68TxMetadata)> for RequestTxHashes {
1347    fn from_iter<I: IntoIterator<Item = (TxHash, Eth68TxMetadata)>>(iter: I) -> Self {
1348        Self::new(iter.into_iter().map(|(hash, _)| hash).collect())
1349    }
1350}
1351
1352/// The earliest block, the latest block and hash of the latest block which can be provided.
1353/// See [BlockRangeUpdate](https://github.com/ethereum/devp2p/blob/master/caps/eth.md#blockrangeupdate-0x11).
1354#[derive(Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
1355#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1356#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1357pub struct BlockRangeUpdate {
1358    /// The earliest block which is available.
1359    pub earliest: u64,
1360    /// The latest block which is available.
1361    pub latest: u64,
1362    /// Latest available block's hash.
1363    pub latest_hash: B256,
1364}
1365
1366impl InMemorySize for NewPooledTransactionHashes {
1367    fn size(&self) -> usize {
1368        match self {
1369            Self::Eth66(msg) => msg.0.len() * core::mem::size_of::<B256>(),
1370            Self::Eth68(msg) => {
1371                msg.types.len() * core::mem::size_of::<u8>() +
1372                    msg.sizes.len() * core::mem::size_of::<usize>() +
1373                    msg.hashes.len() * core::mem::size_of::<B256>()
1374            }
1375            Self::Eth72(msg) => {
1376                msg.types.len() * core::mem::size_of::<u8>() +
1377                    msg.sizes.len() * core::mem::size_of::<usize>() +
1378                    msg.hashes.len() * core::mem::size_of::<B256>() +
1379                    core::mem::size_of::<B128>()
1380            }
1381        }
1382    }
1383}
1384
1385#[cfg(test)]
1386mod tests {
1387    use super::*;
1388    use alloy_consensus::{transaction::TxHashRef, Typed2718};
1389    use alloy_eips::eip2718::Encodable2718;
1390    use alloy_primitives::{b256, hex, Bytes, Signature, U256};
1391    use alloy_rlp::{RlpDecodable, RlpEncodable};
1392    use proptest::prelude::*;
1393    use reth_ethereum_primitives::{Transaction, TransactionSigned};
1394    use std::str::FromStr;
1395
1396    /// Takes as input a struct / encoded hex message pair, ensuring that we encode to the exact hex
1397    /// message, and decode to the exact struct.
1398    fn test_encoding_vector<T: Encodable + Decodable + PartialEq + core::fmt::Debug>(
1399        input: (T, &[u8]),
1400    ) {
1401        let (expected_decoded, expected_encoded) = input;
1402        let mut encoded = Vec::new();
1403        expected_decoded.encode(&mut encoded);
1404
1405        assert_eq!(hex::encode(&encoded), hex::encode(expected_encoded));
1406
1407        let decoded = T::decode(&mut encoded.as_ref()).unwrap();
1408        assert_eq!(expected_decoded, decoded);
1409    }
1410
1411    fn encoded<T: Encodable>(value: &T) -> Vec<u8> {
1412        let mut out = Vec::new();
1413        value.encode(&mut out);
1414        out
1415    }
1416
1417    #[derive(RlpEncodable, RlpDecodable)]
1418    struct EncodableNewPooledTransactionHashes68 {
1419        types: Bytes,
1420        sizes: Vec<usize>,
1421        hashes: Vec<B256>,
1422    }
1423
1424    type NewPooledTransactionHashes68Fields = (Vec<u8>, Vec<usize>, Vec<B256>);
1425
1426    fn decode_eth68_hashes_derived(
1427        buf: &mut &[u8],
1428    ) -> alloy_rlp::Result<NewPooledTransactionHashes68> {
1429        let encodable = EncodableNewPooledTransactionHashes68::decode(buf)?;
1430        let msg = NewPooledTransactionHashes68 {
1431            types: encodable.types.into(),
1432            sizes: encodable.sizes,
1433            hashes: encodable.hashes,
1434        };
1435
1436        ensure_pooled_transaction_hashes_lengths(
1437            msg.hashes.len(),
1438            msg.types.len(),
1439            msg.sizes.len(),
1440        )?;
1441
1442        Ok(msg)
1443    }
1444
1445    fn eth68_hash_fields_strategy() -> impl Strategy<Value = NewPooledTransactionHashes68Fields> {
1446        (0usize..128, 0usize..128, 0usize..128).prop_flat_map(
1447            |(types_len, sizes_len, hashes_len)| {
1448                (
1449                    proptest::collection::vec(any::<u8>(), types_len),
1450                    proptest::collection::vec(0usize..131_072, sizes_len),
1451                    proptest::collection::vec(any::<B256>(), hashes_len),
1452                )
1453            },
1454        )
1455    }
1456
1457    proptest! {
1458        #[test]
1459        fn broadcast_pool_transactions_match_shared_transactions_encoding(
1460            txs in proptest::collection::vec(
1461                proptest_arbitrary_interop::arb::<TransactionSigned>(),
1462                0..32,
1463            )
1464        ) {
1465            let shared = SharedTransactions::<TransactionSigned>(
1466                txs.iter().cloned().map(Arc::new).collect(),
1467            );
1468            let broadcast = BroadcastPoolTransactions(
1469                txs.iter().cloned().map(LazyEncoded::new).collect(),
1470            );
1471
1472            prop_assert_eq!(broadcast.length(), shared.length());
1473
1474            let shared_encoded = encoded(&shared);
1475            let broadcast_encoded = encoded(&broadcast);
1476            prop_assert_eq!(&broadcast_encoded, &shared_encoded);
1477
1478            let broadcast_encoded_cached = encoded(&broadcast);
1479            prop_assert_eq!(&broadcast_encoded_cached, &shared_encoded);
1480
1481            let mut shared_bytes = shared_encoded.as_slice();
1482            let decoded_shared = SharedTransactions::<TransactionSigned>::decode(&mut shared_bytes)
1483                .expect("shared transactions decode");
1484            prop_assert!(shared_bytes.is_empty());
1485
1486            let mut broadcast_bytes = broadcast_encoded.as_slice();
1487            let decoded_broadcast =
1488                SharedTransactions::<TransactionSigned>::decode(&mut broadcast_bytes)
1489                    .expect("broadcast pool transactions decode as shared transactions");
1490            prop_assert!(broadcast_bytes.is_empty());
1491
1492            prop_assert_eq!(decoded_broadcast, decoded_shared);
1493        }
1494
1495        #[test]
1496        fn eth_68_handrolled_decode_matches_derived_implementation(
1497            (types, sizes, hashes) in eth68_hash_fields_strategy()
1498        ) {
1499            let encodable = EncodableNewPooledTransactionHashes68 {
1500                types: Bytes::from(types),
1501                sizes,
1502                hashes,
1503            };
1504            let encoded = encoded(&encodable);
1505
1506            let mut derived_buf = encoded.as_slice();
1507            let derived = decode_eth68_hashes_derived(&mut derived_buf);
1508
1509            let mut handrolled_buf = encoded.as_slice();
1510            let handrolled = NewPooledTransactionHashes68::decode(&mut handrolled_buf);
1511
1512            let handrolled_is_ok = handrolled.is_ok();
1513            prop_assert_eq!(&handrolled, &derived);
1514            if handrolled_is_ok {
1515                prop_assert!(derived_buf.is_empty());
1516                prop_assert!(handrolled_buf.is_empty());
1517            }
1518        }
1519    }
1520
1521    #[test]
1522    fn can_return_latest_block() {
1523        let mut blocks = NewBlockHashes(vec![BlockHashNumber { hash: B256::random(), number: 0 }]);
1524        let latest = blocks.latest().unwrap();
1525        assert_eq!(latest.number, 0);
1526
1527        blocks.push(BlockHashNumber { hash: B256::random(), number: 100 });
1528        blocks.push(BlockHashNumber { hash: B256::random(), number: 2 });
1529        let latest = blocks.latest().unwrap();
1530        assert_eq!(latest.number, 100);
1531    }
1532
1533    #[test]
1534    fn eth_68_tx_hash_roundtrip() {
1535        let vectors = vec![
1536            (
1537                NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] },
1538                &hex!("c380c0c0")[..],
1539            ),
1540            (
1541                NewPooledTransactionHashes68 {
1542                    types: vec![0x00],
1543                    sizes: vec![0x00],
1544                    hashes: vec![
1545                        B256::from_str(
1546                            "0x0000000000000000000000000000000000000000000000000000000000000000",
1547                        )
1548                        .unwrap(),
1549                    ],
1550                },
1551                &hex!(
1552                    "e500c180e1a00000000000000000000000000000000000000000000000000000000000000000"
1553                )[..],
1554            ),
1555            (
1556                NewPooledTransactionHashes68 {
1557                    types: vec![0x00, 0x00],
1558                    sizes: vec![0x00, 0x00],
1559                    hashes: vec![
1560                        B256::from_str(
1561                            "0x0000000000000000000000000000000000000000000000000000000000000000",
1562                        )
1563                        .unwrap(),
1564                        B256::from_str(
1565                            "0x0000000000000000000000000000000000000000000000000000000000000000",
1566                        )
1567                        .unwrap(),
1568                    ],
1569                },
1570                &hex!(
1571                    "f84a820000c28080f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
1572                )[..],
1573            ),
1574            (
1575                NewPooledTransactionHashes68 {
1576                    types: vec![0x02],
1577                    sizes: vec![0xb6],
1578                    hashes: vec![
1579                        B256::from_str(
1580                            "0xfecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124",
1581                        )
1582                        .unwrap(),
1583                    ],
1584                },
1585                &hex!(
1586                    "e602c281b6e1a0fecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124"
1587                )[..],
1588            ),
1589            (
1590                NewPooledTransactionHashes68 {
1591                    types: vec![0xff, 0xff],
1592                    sizes: vec![0xffffffff, 0xffffffff],
1593                    hashes: vec![
1594                        B256::from_str(
1595                            "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1596                        )
1597                        .unwrap(),
1598                        B256::from_str(
1599                            "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1600                        )
1601                        .unwrap(),
1602                    ],
1603                },
1604                &hex!(
1605                    "f85282ffffca84ffffffff84fffffffff842a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1606                )[..],
1607            ),
1608            (
1609                NewPooledTransactionHashes68 {
1610                    types: vec![0xff, 0xff],
1611                    sizes: vec![0xffffffff, 0xffffffff],
1612                    hashes: vec![
1613                        B256::from_str(
1614                            "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe",
1615                        )
1616                        .unwrap(),
1617                        B256::from_str(
1618                            "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe",
1619                        )
1620                        .unwrap(),
1621                    ],
1622                },
1623                &hex!(
1624                    "f85282ffffca84ffffffff84fffffffff842a0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafea0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe"
1625                )[..],
1626            ),
1627            (
1628                NewPooledTransactionHashes68 {
1629                    types: vec![0x10, 0x10],
1630                    sizes: vec![0xdeadc0de, 0xdeadc0de],
1631                    hashes: vec![
1632                        B256::from_str(
1633                            "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2",
1634                        )
1635                        .unwrap(),
1636                        B256::from_str(
1637                            "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2",
1638                        )
1639                        .unwrap(),
1640                    ],
1641                },
1642                &hex!(
1643                    "f852821010ca84deadc0de84deadc0def842a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2"
1644                )[..],
1645            ),
1646            (
1647                NewPooledTransactionHashes68 {
1648                    types: vec![0x6f, 0x6f],
1649                    sizes: vec![0x7fffffff, 0x7fffffff],
1650                    hashes: vec![
1651                        B256::from_str(
1652                            "0x0000000000000000000000000000000000000000000000000000000000000002",
1653                        )
1654                        .unwrap(),
1655                        B256::from_str(
1656                            "0x0000000000000000000000000000000000000000000000000000000000000002",
1657                        )
1658                        .unwrap(),
1659                    ],
1660                },
1661                &hex!(
1662                    "f852826f6fca847fffffff847ffffffff842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000002"
1663                )[..],
1664            ),
1665        ];
1666
1667        for vector in vectors {
1668            test_encoding_vector(vector);
1669        }
1670    }
1671
1672    #[test]
1673    fn eth_72_tx_hash_roundtrip() {
1674        let vectors = vec![
1675            // `None` is always encoded as a zero-filled 16 byte mask, matching go-ethereum's
1676            // non-optional `[16]byte` field.
1677            (
1678                NewPooledTransactionHashes72 {
1679                    types: vec![],
1680                    sizes: vec![],
1681                    hashes: vec![],
1682                    cell_mask: None,
1683                },
1684                &hex!("d480c0c09000000000000000000000000000000000")[..],
1685            ),
1686            (
1687                NewPooledTransactionHashes72 {
1688                    types: vec![],
1689                    sizes: vec![],
1690                    hashes: vec![],
1691                    cell_mask: Some(B128::repeat_byte(0x11)),
1692                },
1693                &hex!("d480c0c09011111111111111111111111111111111")[..],
1694            ),
1695        ];
1696
1697        for vector in vectors {
1698            test_encoding_vector(vector);
1699        }
1700    }
1701
1702    #[test]
1703    fn eth_72_decodes_spec_nil_cell_mask() {
1704        // The EIP-8070 text encodes an absent mask as the RLP empty string; decoding stays
1705        // lenient even though reth never produces this form.
1706        let encoded = hex!("c480c0c080");
1707
1708        let decoded = NewPooledTransactionHashes72::decode(&mut encoded.as_ref()).unwrap();
1709
1710        assert_eq!(decoded.cell_mask, None);
1711    }
1712
1713    #[test]
1714    fn eth_72_rejects_missing_cell_mask() {
1715        let encoded_eth68_payload = hex!("c380c0c0");
1716
1717        let result = NewPooledTransactionHashes72::decode(&mut encoded_eth68_payload.as_ref());
1718
1719        assert!(matches!(result, Err(alloy_rlp::Error::InputTooShort)));
1720    }
1721
1722    #[test]
1723    fn eth_72_dedup_preserves_message_cell_mask() {
1724        let cell_mask = Some(B128::repeat_byte(0x11));
1725        let announcement = NewPooledTransactionHashes72 {
1726            types: vec![3],
1727            sizes: vec![128],
1728            hashes: vec![B256::from([1u8; 32])],
1729            cell_mask,
1730        };
1731
1732        let partially_valid = announcement.dedup();
1733        assert_eq!(partially_valid.eth72_cell_mask(), cell_mask);
1734
1735        let valid = ValidAnnouncementData::from_partially_valid_data(partially_valid);
1736        assert_eq!(valid.eth72_cell_mask(), cell_mask);
1737    }
1738
1739    #[test]
1740    fn request_hashes_retain_count_keep_subset() {
1741        let mut hashes = RequestTxHashes::new(
1742            [
1743                b256!("0x0000000000000000000000000000000000000000000000000000000000000001"),
1744                b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
1745                b256!("0x0000000000000000000000000000000000000000000000000000000000000003"),
1746                b256!("0x0000000000000000000000000000000000000000000000000000000000000004"),
1747                b256!("0x0000000000000000000000000000000000000000000000000000000000000005"),
1748            ]
1749            .into_iter()
1750            .collect::<B256Set>(),
1751        );
1752
1753        let rest = hashes.retain_count(3);
1754
1755        assert_eq!(3, hashes.len());
1756        assert_eq!(2, rest.len());
1757    }
1758
1759    #[test]
1760    fn request_hashes_retain_count_keep_all() {
1761        let mut hashes = RequestTxHashes::new(
1762            [
1763                b256!("0x0000000000000000000000000000000000000000000000000000000000000001"),
1764                b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
1765                b256!("0x0000000000000000000000000000000000000000000000000000000000000003"),
1766                b256!("0x0000000000000000000000000000000000000000000000000000000000000004"),
1767                b256!("0x0000000000000000000000000000000000000000000000000000000000000005"),
1768            ]
1769            .into_iter()
1770            .collect::<B256Set>(),
1771        );
1772
1773        let _ = hashes.retain_count(6);
1774
1775        assert_eq!(5, hashes.len());
1776    }
1777
1778    #[test]
1779    fn split_request_hashes_keep_none() {
1780        let mut hashes = RequestTxHashes::new(
1781            [
1782                b256!("0x0000000000000000000000000000000000000000000000000000000000000001"),
1783                b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
1784                b256!("0x0000000000000000000000000000000000000000000000000000000000000003"),
1785                b256!("0x0000000000000000000000000000000000000000000000000000000000000004"),
1786                b256!("0x0000000000000000000000000000000000000000000000000000000000000005"),
1787            ]
1788            .into_iter()
1789            .collect::<B256Set>(),
1790        );
1791
1792        let rest = hashes.retain_count(0);
1793
1794        assert_eq!(0, hashes.len());
1795        assert_eq!(5, rest.len());
1796    }
1797
1798    fn signed_transaction() -> impl SignedTransaction {
1799        TransactionSigned::new_unhashed(
1800            Transaction::Legacy(Default::default()),
1801            Signature::new(
1802                U256::from_str(
1803                    "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
1804                )
1805                .unwrap(),
1806                U256::from_str(
1807                    "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
1808                )
1809                .unwrap(),
1810                false,
1811            ),
1812        )
1813    }
1814
1815    #[test]
1816    fn test_pooled_tx_hashes_68_push() {
1817        let tx = signed_transaction();
1818        let mut tx_hashes =
1819            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] };
1820        tx_hashes.push(&tx);
1821        assert_eq!(tx_hashes.types.len(), 1);
1822        assert_eq!(tx_hashes.sizes.len(), 1);
1823        assert_eq!(tx_hashes.hashes.len(), 1);
1824        assert_eq!(tx_hashes.types[0], tx.ty());
1825        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1826        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1827    }
1828
1829    #[test]
1830    fn test_pooled_tx_hashes_68_extend() {
1831        let tx = signed_transaction();
1832        let txs = vec![tx.clone(), tx.clone()];
1833        let mut tx_hashes =
1834            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] };
1835        tx_hashes.extend(&txs);
1836        assert_eq!(tx_hashes.types.len(), 2);
1837        assert_eq!(tx_hashes.sizes.len(), 2);
1838        assert_eq!(tx_hashes.hashes.len(), 2);
1839        assert_eq!(tx_hashes.types[0], tx.ty());
1840        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1841        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1842        assert_eq!(tx_hashes.types[1], tx.ty());
1843        assert_eq!(tx_hashes.sizes[1], tx.encode_2718_len());
1844        assert_eq!(tx_hashes.hashes[1], *tx.tx_hash());
1845    }
1846
1847    #[test]
1848    fn test_pooled_tx_hashes_68_with_transaction() {
1849        let tx = signed_transaction();
1850        let tx_hashes =
1851            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] }
1852                .with_transaction(&tx);
1853        assert_eq!(tx_hashes.types.len(), 1);
1854        assert_eq!(tx_hashes.sizes.len(), 1);
1855        assert_eq!(tx_hashes.hashes.len(), 1);
1856        assert_eq!(tx_hashes.types[0], tx.ty());
1857        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1858        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1859    }
1860
1861    #[test]
1862    fn test_pooled_tx_hashes_68_with_transactions() {
1863        let tx = signed_transaction();
1864        let txs = vec![tx.clone(), tx.clone()];
1865        let tx_hashes =
1866            NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] }
1867                .with_transactions(&txs);
1868        assert_eq!(tx_hashes.types.len(), 2);
1869        assert_eq!(tx_hashes.sizes.len(), 2);
1870        assert_eq!(tx_hashes.hashes.len(), 2);
1871        assert_eq!(tx_hashes.types[0], tx.ty());
1872        assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1873        assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1874        assert_eq!(tx_hashes.types[1], tx.ty());
1875        assert_eq!(tx_hashes.sizes[1], tx.encode_2718_len());
1876        assert_eq!(tx_hashes.hashes[1], *tx.tx_hash());
1877    }
1878}