reth_network/transactions/
validation.rs

1//! Validation of [`NewPooledTransactionHashes66`](reth_eth_wire::NewPooledTransactionHashes66)
2//! and [`NewPooledTransactionHashes68`](reth_eth_wire::NewPooledTransactionHashes68)
3//! announcements. Validation and filtering of announcements is network dependent.
4
5use crate::metrics::{AnnouncedTxTypesMetrics, TxTypesCounter};
6use alloy_primitives::{PrimitiveSignature as Signature, TxHash};
7use derive_more::{Deref, DerefMut};
8use reth_eth_wire::{
9    DedupPayload, Eth68TxMetadata, HandleMempoolData, PartiallyValidData, ValidAnnouncementData,
10    MAX_MESSAGE_SIZE,
11};
12use reth_primitives::TxType;
13use std::{fmt, fmt::Display, mem};
14use tracing::trace;
15
16/// The size of a decoded signature in bytes.
17pub const SIGNATURE_DECODED_SIZE_BYTES: usize = mem::size_of::<Signature>();
18
19/// Interface for validating a `(ty, size, hash)` tuple from a
20/// [`NewPooledTransactionHashes68`](reth_eth_wire::NewPooledTransactionHashes68)..
21pub trait ValidateTx68 {
22    /// Validates a [`NewPooledTransactionHashes68`](reth_eth_wire::NewPooledTransactionHashes68)
23    /// entry. Returns [`ValidationOutcome`] which signals to the caller whether to fetch the
24    /// transaction or to drop it, and whether the sender of the announcement should be
25    /// penalized.
26    fn should_fetch(
27        &self,
28        ty: u8,
29        hash: &TxHash,
30        size: usize,
31        tx_types_counter: &mut TxTypesCounter,
32    ) -> ValidationOutcome;
33
34    /// Returns the reasonable maximum encoded transaction length configured for this network, if
35    /// any. This property is not spec'ed out but can be inferred by looking how much data can be
36    /// packed into a transaction for any given transaction type.
37    fn max_encoded_tx_length(&self, ty: TxType) -> Option<usize>;
38
39    /// Returns the strict maximum encoded transaction length for the given transaction type, if
40    /// any.
41    fn strict_max_encoded_tx_length(&self, ty: TxType) -> Option<usize>;
42
43    /// Returns the reasonable minimum encoded transaction length, if any. This property is not
44    /// spec'ed out but can be inferred by looking at which
45    /// [`reth_primitives::PooledTransaction`] will successfully pass decoding
46    /// for any given transaction type.
47    fn min_encoded_tx_length(&self, ty: TxType) -> Option<usize>;
48
49    /// Returns the strict minimum encoded transaction length for the given transaction type, if
50    /// any.
51    fn strict_min_encoded_tx_length(&self, ty: TxType) -> Option<usize>;
52}
53
54/// Outcomes from validating a `(ty, hash, size)` entry from a
55/// [`NewPooledTransactionHashes68`](reth_eth_wire::NewPooledTransactionHashes68). Signals to the
56/// caller how to deal with an announcement entry and the peer who sent the announcement.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum ValidationOutcome {
59    /// Tells the caller to keep the entry in the announcement for fetch.
60    Fetch,
61    /// Tells the caller to filter out the entry from the announcement.
62    Ignore,
63    /// Tells the caller to filter out the entry from the announcement and penalize the peer. On
64    /// this outcome, caller can drop the announcement, that is up to each implementation.
65    ReportPeer,
66}
67
68/// Generic filter for announcements and responses. Checks for empty message and unique hashes/
69/// transactions in message.
70pub trait PartiallyFilterMessage {
71    /// Removes duplicate entries from a mempool message. Returns [`FilterOutcome::ReportPeer`] if
72    /// the caller should penalize the peer, otherwise [`FilterOutcome::Ok`].
73    fn partially_filter_valid_entries<V>(
74        &self,
75        msg: impl DedupPayload<Value = V> + fmt::Debug,
76    ) -> (FilterOutcome, PartiallyValidData<V>) {
77        // 1. checks if the announcement is empty
78        if msg.is_empty() {
79            trace!(target: "net::tx",
80                msg=?msg,
81                "empty payload"
82            );
83            return (FilterOutcome::ReportPeer, PartiallyValidData::empty_eth66())
84        }
85
86        // 2. checks if announcement is spam packed with duplicate hashes
87        let original_len = msg.len();
88        let partially_valid_data = msg.dedup();
89
90        (
91            if partially_valid_data.len() == original_len {
92                FilterOutcome::Ok
93            } else {
94                FilterOutcome::ReportPeer
95            },
96            partially_valid_data,
97        )
98    }
99}
100
101/// Filters valid entries in
102/// [`NewPooledTransactionHashes68`](reth_eth_wire::NewPooledTransactionHashes68) and
103/// [`NewPooledTransactionHashes66`](reth_eth_wire::NewPooledTransactionHashes66) in place, and
104/// flags misbehaving peers.
105pub trait FilterAnnouncement {
106    /// Removes invalid entries from a
107    /// [`NewPooledTransactionHashes68`](reth_eth_wire::NewPooledTransactionHashes68) announcement.
108    /// Returns [`FilterOutcome::ReportPeer`] if the caller should penalize the peer, otherwise
109    /// [`FilterOutcome::Ok`].
110    fn filter_valid_entries_68(
111        &self,
112        msg: PartiallyValidData<Eth68TxMetadata>,
113    ) -> (FilterOutcome, ValidAnnouncementData)
114    where
115        Self: ValidateTx68;
116
117    /// Removes invalid entries from a
118    /// [`NewPooledTransactionHashes66`](reth_eth_wire::NewPooledTransactionHashes66) announcement.
119    /// Returns [`FilterOutcome::ReportPeer`] if the caller should penalize the peer, otherwise
120    /// [`FilterOutcome::Ok`].
121    fn filter_valid_entries_66(
122        &self,
123        msg: PartiallyValidData<Eth68TxMetadata>,
124    ) -> (FilterOutcome, ValidAnnouncementData);
125}
126
127/// Outcome from filtering
128/// [`NewPooledTransactionHashes68`](reth_eth_wire::NewPooledTransactionHashes68). Signals to caller
129/// whether to penalize the sender of the announcement or not.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum FilterOutcome {
132    /// Peer behaves appropriately.
133    Ok,
134    /// A penalty should be flagged for the peer. Peer sent an announcement with unacceptably
135    /// invalid entries.
136    ReportPeer,
137}
138
139/// Wrapper for types that implement [`FilterAnnouncement`]. The definition of a valid
140/// announcement is network dependent. For example, different networks support different
141/// [`TxType`]s, and different [`TxType`]s have different transaction size constraints. Defaults to
142/// [`EthMessageFilter`].
143#[derive(Debug, Default, Deref, DerefMut)]
144pub struct MessageFilter<N = EthMessageFilter>(N);
145
146/// Filter for announcements containing EIP [`TxType`]s.
147#[derive(Debug, Default)]
148pub struct EthMessageFilter {
149    announced_tx_types_metrics: AnnouncedTxTypesMetrics,
150}
151
152impl Display for EthMessageFilter {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        write!(f, "EthMessageFilter")
155    }
156}
157
158impl PartiallyFilterMessage for EthMessageFilter {}
159
160impl ValidateTx68 for EthMessageFilter {
161    fn should_fetch(
162        &self,
163        ty: u8,
164        hash: &TxHash,
165        size: usize,
166        tx_types_counter: &mut TxTypesCounter,
167    ) -> ValidationOutcome {
168        //
169        // 1. checks if tx type is valid value for this network
170        //
171        let tx_type = match TxType::try_from(ty) {
172            Ok(ty) => ty,
173            Err(_) => {
174                trace!(target: "net::eth-wire",
175                    ty=ty,
176                    size=size,
177                    hash=%hash,
178                    network=%self,
179                    "invalid tx type in eth68 announcement"
180                );
181
182                return ValidationOutcome::ReportPeer
183            }
184        };
185        tx_types_counter.increase_by_tx_type(tx_type);
186
187        //
188        // 2. checks if tx's encoded length is within limits for this network
189        //
190        // transactions that are filtered out here, may not be spam, rather from benevolent peers
191        // that are unknowingly sending announcements with invalid data.
192        //
193        if let Some(strict_min_encoded_tx_length) = self.strict_min_encoded_tx_length(tx_type) {
194            if size < strict_min_encoded_tx_length {
195                trace!(target: "net::eth-wire",
196                    ty=ty,
197                    size=size,
198                    hash=%hash,
199                    strict_min_encoded_tx_length=strict_min_encoded_tx_length,
200                    network=%self,
201                    "invalid tx size in eth68 announcement"
202                );
203
204                return ValidationOutcome::Ignore
205            }
206        }
207        if let Some(reasonable_min_encoded_tx_length) = self.min_encoded_tx_length(tx_type) {
208            if size < reasonable_min_encoded_tx_length {
209                trace!(target: "net::eth-wire",
210                    ty=ty,
211                    size=size,
212                    hash=%hash,
213                    reasonable_min_encoded_tx_length=reasonable_min_encoded_tx_length,
214                    strict_min_encoded_tx_length=self.strict_min_encoded_tx_length(tx_type),
215                    network=%self,
216                    "tx size in eth68 announcement, is unreasonably small"
217                );
218
219                // just log a tx which is smaller than the reasonable min encoded tx length, don't
220                // filter it out
221            }
222        }
223        // this network has no strict max encoded tx length for any tx type
224        if let Some(reasonable_max_encoded_tx_length) = self.max_encoded_tx_length(tx_type) {
225            if size > reasonable_max_encoded_tx_length {
226                trace!(target: "net::eth-wire",
227                    ty=ty,
228                    size=size,
229                    hash=%hash,
230                    reasonable_max_encoded_tx_length=reasonable_max_encoded_tx_length,
231                    strict_max_encoded_tx_length=self.strict_max_encoded_tx_length(tx_type),
232                    network=%self,
233                    "tx size in eth68 announcement, is unreasonably large"
234                );
235
236                // just log a tx which is bigger than the reasonable max encoded tx length, don't
237                // filter it out
238            }
239        }
240
241        ValidationOutcome::Fetch
242    }
243
244    fn max_encoded_tx_length(&self, ty: TxType) -> Option<usize> {
245        // the biggest transaction so far is a blob transaction, which is currently max 2^17,
246        // encoded length, nonetheless, the blob tx may become bigger in the future.
247        #[allow(unreachable_patterns, clippy::match_same_arms)]
248        match ty {
249            TxType::Legacy | TxType::Eip2930 | TxType::Eip1559 => Some(MAX_MESSAGE_SIZE),
250            TxType::Eip4844 => None,
251            _ => None,
252        }
253    }
254
255    fn strict_max_encoded_tx_length(&self, _ty: TxType) -> Option<usize> {
256        None
257    }
258
259    fn min_encoded_tx_length(&self, _ty: TxType) -> Option<usize> {
260        // a transaction will have at least a signature. the encoded signature encoded on the tx
261        // is at least as big as the decoded type.
262        Some(SIGNATURE_DECODED_SIZE_BYTES)
263    }
264
265    fn strict_min_encoded_tx_length(&self, _ty: TxType) -> Option<usize> {
266        // decoding a tx will exit right away if it's not at least a byte
267        Some(1)
268    }
269}
270
271impl FilterAnnouncement for EthMessageFilter {
272    fn filter_valid_entries_68(
273        &self,
274        mut msg: PartiallyValidData<Eth68TxMetadata>,
275    ) -> (FilterOutcome, ValidAnnouncementData)
276    where
277        Self: ValidateTx68,
278    {
279        trace!(target: "net::tx::validation",
280            msg=?*msg,
281            network=%self,
282            "validating eth68 announcement data.."
283        );
284
285        let mut should_report_peer = false;
286        let mut tx_types_counter = TxTypesCounter::default();
287
288        // checks if eth68 announcement metadata is valid
289        //
290        // transactions that are filtered out here, may not be spam, rather from benevolent peers
291        // that are unknowingly sending announcements with invalid data.
292        //
293        msg.retain(|hash, metadata| {
294            debug_assert!(
295                metadata.is_some(),
296                "metadata should exist for `%hash` in eth68 announcement passed to `%filter_valid_entries_68`,
297`%hash`: {hash}"
298            );
299
300            let Some((ty, size)) = metadata else {
301                return false
302            };
303
304            match self.should_fetch(*ty, hash, *size, &mut tx_types_counter) {
305                ValidationOutcome::Fetch => true,
306                ValidationOutcome::Ignore => false,
307                ValidationOutcome::ReportPeer => {
308                    should_report_peer = true;
309                    false
310                }
311            }
312        });
313        self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
314        (
315            if should_report_peer { FilterOutcome::ReportPeer } else { FilterOutcome::Ok },
316            ValidAnnouncementData::from_partially_valid_data(msg),
317        )
318    }
319
320    fn filter_valid_entries_66(
321        &self,
322        partially_valid_data: PartiallyValidData<Option<(u8, usize)>>,
323    ) -> (FilterOutcome, ValidAnnouncementData) {
324        trace!(target: "net::tx::validation",
325            hashes=?*partially_valid_data,
326            network=%self,
327            "validating eth66 announcement data.."
328        );
329
330        (FilterOutcome::Ok, ValidAnnouncementData::from_partially_valid_data(partially_valid_data))
331    }
332}
333
334#[cfg(test)]
335mod test {
336    use super::*;
337    use alloy_primitives::B256;
338    use reth_eth_wire::{NewPooledTransactionHashes66, NewPooledTransactionHashes68};
339    use std::{collections::HashMap, str::FromStr};
340
341    #[test]
342    fn eth68_empty_announcement() {
343        let types = vec![];
344        let sizes = vec![];
345        let hashes = vec![];
346
347        let announcement = NewPooledTransactionHashes68 { types, sizes, hashes };
348
349        let filter = EthMessageFilter::default();
350
351        let (outcome, _partially_valid_data) = filter.partially_filter_valid_entries(announcement);
352
353        assert_eq!(outcome, FilterOutcome::ReportPeer);
354    }
355
356    #[test]
357    fn eth68_announcement_unrecognized_tx_type() {
358        let types = vec![
359            TxType::Eip7702 as u8 + 1, // the first type isn't valid
360            TxType::Legacy as u8,
361        ];
362        let sizes = vec![MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE];
363        let hashes = vec![
364            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
365                .unwrap(),
366            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
367                .unwrap(),
368        ];
369
370        let announcement = NewPooledTransactionHashes68 {
371            types: types.clone(),
372            sizes: sizes.clone(),
373            hashes: hashes.clone(),
374        };
375
376        let filter = EthMessageFilter::default();
377
378        let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
379
380        assert_eq!(outcome, FilterOutcome::Ok);
381
382        let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
383
384        assert_eq!(outcome, FilterOutcome::ReportPeer);
385
386        let mut expected_data = HashMap::default();
387        expected_data.insert(hashes[1], Some((types[1], sizes[1])));
388
389        assert_eq!(expected_data, valid_data.into_data())
390    }
391
392    #[test]
393    fn eth68_announcement_too_small_tx() {
394        let types = vec![TxType::Eip7702 as u8, TxType::Legacy as u8, TxType::Eip2930 as u8];
395        let sizes = vec![
396            0, // the first length isn't valid
397            0, // neither is the second
398            1,
399        ];
400        let hashes = vec![
401            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
402                .unwrap(),
403            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
404                .unwrap(),
405            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeef00bb")
406                .unwrap(),
407        ];
408
409        let announcement = NewPooledTransactionHashes68 {
410            types: types.clone(),
411            sizes: sizes.clone(),
412            hashes: hashes.clone(),
413        };
414
415        let filter = EthMessageFilter::default();
416
417        let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
418
419        assert_eq!(outcome, FilterOutcome::Ok);
420
421        let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
422
423        assert_eq!(outcome, FilterOutcome::Ok);
424
425        let mut expected_data = HashMap::default();
426        expected_data.insert(hashes[2], Some((types[2], sizes[2])));
427
428        assert_eq!(expected_data, valid_data.into_data())
429    }
430
431    #[test]
432    fn eth68_announcement_duplicate_tx_hash() {
433        let types = vec![
434            TxType::Eip1559 as u8,
435            TxType::Eip4844 as u8,
436            TxType::Eip1559 as u8,
437            TxType::Eip4844 as u8,
438        ];
439        let sizes = vec![1, 1, 1, MAX_MESSAGE_SIZE];
440        // first three or the same
441        let hashes = vec![
442            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") // dup
443                .unwrap(),
444            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") // removed dup
445                .unwrap(),
446            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") // removed dup
447                .unwrap(),
448            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
449                .unwrap(),
450        ];
451
452        let announcement = NewPooledTransactionHashes68 {
453            types: types.clone(),
454            sizes: sizes.clone(),
455            hashes: hashes.clone(),
456        };
457
458        let filter = EthMessageFilter::default();
459
460        let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
461
462        assert_eq!(outcome, FilterOutcome::ReportPeer);
463
464        let mut expected_data = HashMap::default();
465        expected_data.insert(hashes[3], Some((types[3], sizes[3])));
466        expected_data.insert(hashes[0], Some((types[0], sizes[0])));
467
468        assert_eq!(expected_data, partially_valid_data.into_data())
469    }
470
471    #[test]
472    fn eth66_empty_announcement() {
473        let hashes = vec![];
474
475        let announcement = NewPooledTransactionHashes66(hashes);
476
477        let filter: MessageFilter = MessageFilter::default();
478
479        let (outcome, _partially_valid_data) = filter.partially_filter_valid_entries(announcement);
480
481        assert_eq!(outcome, FilterOutcome::ReportPeer);
482    }
483
484    #[test]
485    fn eth66_announcement_duplicate_tx_hash() {
486        // first three or the same
487        let hashes = vec![
488            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb") // dup1
489                .unwrap(),
490            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") // dup2
491                .unwrap(),
492            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") // removed dup2
493                .unwrap(),
494            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") // removed dup2
495                .unwrap(),
496            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb") // removed dup1
497                .unwrap(),
498        ];
499
500        let announcement = NewPooledTransactionHashes66(hashes.clone());
501
502        let filter: MessageFilter = MessageFilter::default();
503
504        let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
505
506        assert_eq!(outcome, FilterOutcome::ReportPeer);
507
508        let mut expected_data = HashMap::default();
509        expected_data.insert(hashes[1], None);
510        expected_data.insert(hashes[0], None);
511
512        assert_eq!(expected_data, partially_valid_data.into_data())
513    }
514
515    #[test]
516    fn eth68_announcement_eip7702_tx() {
517        let types = vec![TxType::Eip7702 as u8, TxType::Legacy as u8];
518        let sizes = vec![MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE];
519        let hashes = vec![
520            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
521                .unwrap(),
522            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
523                .unwrap(),
524        ];
525
526        let announcement = NewPooledTransactionHashes68 {
527            types: types.clone(),
528            sizes: sizes.clone(),
529            hashes: hashes.clone(),
530        };
531
532        let filter = EthMessageFilter::default();
533
534        let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
535        assert_eq!(outcome, FilterOutcome::Ok);
536
537        let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
538        assert_eq!(outcome, FilterOutcome::Ok);
539
540        let mut expected_data = HashMap::default();
541        expected_data.insert(hashes[0], Some((types[0], sizes[0])));
542        expected_data.insert(hashes[1], Some((types[1], sizes[1])));
543
544        assert_eq!(expected_data, valid_data.into_data());
545    }
546
547    #[test]
548    fn eth68_announcement_eip7702_tx_size_validation() {
549        let types = vec![TxType::Eip7702 as u8, TxType::Eip7702 as u8, TxType::Eip7702 as u8];
550        // Test with different sizes: too small, reasonable, too large
551        let sizes = vec![
552            1,                    // too small
553            MAX_MESSAGE_SIZE / 2, // reasonable size
554            MAX_MESSAGE_SIZE + 1, // too large
555        ];
556        let hashes = vec![
557            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
558                .unwrap(),
559            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
560                .unwrap(),
561            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcccc")
562                .unwrap(),
563        ];
564
565        let announcement = NewPooledTransactionHashes68 {
566            types: types.clone(),
567            sizes: sizes.clone(),
568            hashes: hashes.clone(),
569        };
570
571        let filter = EthMessageFilter::default();
572
573        let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
574        assert_eq!(outcome, FilterOutcome::Ok);
575
576        let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
577        assert_eq!(outcome, FilterOutcome::Ok);
578
579        let mut expected_data = HashMap::default();
580
581        for i in 0..3 {
582            expected_data.insert(hashes[i], Some((types[i], sizes[i])));
583        }
584
585        assert_eq!(expected_data, valid_data.into_data());
586    }
587
588    #[test]
589    fn eth68_announcement_mixed_tx_types() {
590        let types = vec![
591            TxType::Legacy as u8,
592            TxType::Eip7702 as u8,
593            TxType::Eip1559 as u8,
594            TxType::Eip4844 as u8,
595        ];
596        let sizes = vec![MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE];
597        let hashes = vec![
598            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
599                .unwrap(),
600            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
601                .unwrap(),
602            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcccc")
603                .unwrap(),
604            B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefdddd")
605                .unwrap(),
606        ];
607
608        let announcement = NewPooledTransactionHashes68 {
609            types: types.clone(),
610            sizes: sizes.clone(),
611            hashes: hashes.clone(),
612        };
613
614        let filter = EthMessageFilter::default();
615
616        let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
617        assert_eq!(outcome, FilterOutcome::Ok);
618
619        let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
620        assert_eq!(outcome, FilterOutcome::Ok);
621
622        let mut expected_data = HashMap::default();
623        // All transaction types should be included as they are valid
624        for i in 0..4 {
625            expected_data.insert(hashes[i], Some((types[i], sizes[i])));
626        }
627
628        assert_eq!(expected_data, valid_data.into_data());
629    }
630
631    #[test]
632    fn test_display_for_zst() {
633        let filter = EthMessageFilter::default();
634        assert_eq!("EthMessageFilter", &filter.to_string());
635    }
636}