1use crate::metrics::{AnnouncedTxTypesMetrics, TxTypesCounter};
6use alloy_primitives::Signature;
7use derive_more::{Deref, DerefMut};
8use reth_eth_wire::{
9 DedupPayload, Eth68TxMetadata, HandleMempoolData, PartiallyValidData, ValidAnnouncementData,
10};
11use reth_ethereum_primitives::TxType;
12use std::{fmt, fmt::Display, mem};
13use tracing::trace;
14
15pub const SIGNATURE_DECODED_SIZE_BYTES: usize = mem::size_of::<Signature>();
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum ValidationOutcome {
23 Fetch,
25 Ignore,
27 ReportPeer,
30}
31
32pub trait PartiallyFilterMessage {
35 fn partially_filter_valid_entries<V>(
38 &self,
39 msg: impl DedupPayload<Value = V> + fmt::Debug,
40 ) -> (FilterOutcome, PartiallyValidData<V>) {
41 if msg.is_empty() {
43 trace!(target: "net::tx",
44 msg=?msg,
45 "empty payload"
46 );
47 return (FilterOutcome::ReportPeer, PartiallyValidData::empty_eth66())
48 }
49
50 let original_len = msg.len();
52 let partially_valid_data = msg.dedup();
53
54 (
55 if partially_valid_data.len() == original_len {
56 FilterOutcome::Ok
57 } else {
58 FilterOutcome::ReportPeer
59 },
60 partially_valid_data,
61 )
62 }
63}
64
65pub trait FilterAnnouncement {
70 fn filter_valid_entries_68(
75 &self,
76 msg: PartiallyValidData<Eth68TxMetadata>,
77 ) -> (FilterOutcome, ValidAnnouncementData);
78
79 fn filter_valid_entries_66(
84 &self,
85 msg: PartiallyValidData<Eth68TxMetadata>,
86 ) -> (FilterOutcome, ValidAnnouncementData);
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum FilterOutcome {
94 Ok,
96 ReportPeer,
99}
100
101#[derive(Debug, Default, Deref, DerefMut)]
106pub struct MessageFilter<N = EthMessageFilter>(N);
107
108#[derive(Debug, Default)]
110pub struct EthMessageFilter {
111 announced_tx_types_metrics: AnnouncedTxTypesMetrics,
112}
113
114impl Display for EthMessageFilter {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 write!(f, "EthMessageFilter")
117 }
118}
119
120impl PartiallyFilterMessage for EthMessageFilter {}
121
122impl FilterAnnouncement for EthMessageFilter {
123 fn filter_valid_entries_68(
124 &self,
125 mut msg: PartiallyValidData<Eth68TxMetadata>,
126 ) -> (FilterOutcome, ValidAnnouncementData) {
127 trace!(target: "net::tx::validation",
128 msg=?*msg,
129 network=%self,
130 "validating eth68 announcement data.."
131 );
132
133 let mut should_report_peer = false;
134 let mut tx_types_counter = TxTypesCounter::default();
135
136 msg.retain(|hash, metadata| {
142 debug_assert!(
143 metadata.is_some(),
144 "metadata should exist for `%hash` in eth68 announcement passed to `%filter_valid_entries_68`,
145`%hash`: {hash}"
146 );
147
148 let Some((ty, size)) = metadata else {
149 return false
150 };
151
152 let tx_type = match TxType::try_from(*ty) {
156 Ok(ty) => ty,
157 Err(_) => {
158 trace!(target: "net::eth-wire",
159 ty=ty,
160 size=size,
161 hash=%hash,
162 network=%self,
163 "invalid tx type in eth68 announcement"
164 );
165
166 should_report_peer = true;
167 return false;
168 }
169
170 };
171 tx_types_counter.increase_by_tx_type(tx_type);
172
173 true
174 });
175 self.announced_tx_types_metrics.update_eth68_announcement_metrics(tx_types_counter);
176 (
177 if should_report_peer { FilterOutcome::ReportPeer } else { FilterOutcome::Ok },
178 ValidAnnouncementData::from_partially_valid_data(msg),
179 )
180 }
181
182 fn filter_valid_entries_66(
183 &self,
184 partially_valid_data: PartiallyValidData<Option<(u8, usize)>>,
185 ) -> (FilterOutcome, ValidAnnouncementData) {
186 trace!(target: "net::tx::validation",
187 hashes=?*partially_valid_data,
188 network=%self,
189 "validating eth66 announcement data.."
190 );
191
192 (FilterOutcome::Ok, ValidAnnouncementData::from_partially_valid_data(partially_valid_data))
193 }
194}
195
196#[cfg(test)]
197mod test {
198 use super::*;
199 use alloy_primitives::B256;
200 use reth_eth_wire::{
201 NewPooledTransactionHashes66, NewPooledTransactionHashes68, MAX_MESSAGE_SIZE,
202 };
203 use std::{collections::HashMap, str::FromStr};
204
205 #[test]
206 fn eth68_empty_announcement() {
207 let types = vec![];
208 let sizes = vec![];
209 let hashes = vec![];
210
211 let announcement = NewPooledTransactionHashes68 { types, sizes, hashes };
212
213 let filter = EthMessageFilter::default();
214
215 let (outcome, _partially_valid_data) = filter.partially_filter_valid_entries(announcement);
216
217 assert_eq!(outcome, FilterOutcome::ReportPeer);
218 }
219
220 #[test]
221 fn eth68_announcement_unrecognized_tx_type() {
222 let types = vec![
223 TxType::Eip7702 as u8 + 1, TxType::Legacy as u8,
225 ];
226 let sizes = vec![MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE];
227 let hashes = vec![
228 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
229 .unwrap(),
230 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
231 .unwrap(),
232 ];
233
234 let announcement = NewPooledTransactionHashes68 {
235 types: types.clone(),
236 sizes: sizes.clone(),
237 hashes: hashes.clone(),
238 };
239
240 let filter = EthMessageFilter::default();
241
242 let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
243
244 assert_eq!(outcome, FilterOutcome::Ok);
245
246 let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
247
248 assert_eq!(outcome, FilterOutcome::ReportPeer);
249
250 let mut expected_data = HashMap::default();
251 expected_data.insert(hashes[1], Some((types[1], sizes[1])));
252
253 assert_eq!(expected_data, valid_data.into_data())
254 }
255
256 #[test]
257 fn eth68_announcement_duplicate_tx_hash() {
258 let types = vec![
259 TxType::Eip1559 as u8,
260 TxType::Eip4844 as u8,
261 TxType::Eip1559 as u8,
262 TxType::Eip4844 as u8,
263 ];
264 let sizes = vec![1, 1, 1, MAX_MESSAGE_SIZE];
265 let hashes = vec![
267 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") .unwrap(),
269 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") .unwrap(),
271 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") .unwrap(),
273 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
274 .unwrap(),
275 ];
276
277 let announcement = NewPooledTransactionHashes68 {
278 types: types.clone(),
279 sizes: sizes.clone(),
280 hashes: hashes.clone(),
281 };
282
283 let filter = EthMessageFilter::default();
284
285 let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
286
287 assert_eq!(outcome, FilterOutcome::ReportPeer);
288
289 let mut expected_data = HashMap::default();
290 expected_data.insert(hashes[3], Some((types[3], sizes[3])));
291 expected_data.insert(hashes[0], Some((types[0], sizes[0])));
292
293 assert_eq!(expected_data, partially_valid_data.into_data())
294 }
295
296 #[test]
297 fn eth66_empty_announcement() {
298 let hashes = vec![];
299
300 let announcement = NewPooledTransactionHashes66(hashes);
301
302 let filter: MessageFilter = MessageFilter::default();
303
304 let (outcome, _partially_valid_data) = filter.partially_filter_valid_entries(announcement);
305
306 assert_eq!(outcome, FilterOutcome::ReportPeer);
307 }
308
309 #[test]
310 fn eth66_announcement_duplicate_tx_hash() {
311 let hashes = vec![
313 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb") .unwrap(),
315 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") .unwrap(),
317 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") .unwrap(),
319 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa") .unwrap(),
321 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb") .unwrap(),
323 ];
324
325 let announcement = NewPooledTransactionHashes66(hashes.clone());
326
327 let filter: MessageFilter = MessageFilter::default();
328
329 let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
330
331 assert_eq!(outcome, FilterOutcome::ReportPeer);
332
333 let mut expected_data = HashMap::default();
334 expected_data.insert(hashes[1], None);
335 expected_data.insert(hashes[0], None);
336
337 assert_eq!(expected_data, partially_valid_data.into_data())
338 }
339
340 #[test]
341 fn eth68_announcement_eip7702_tx() {
342 let types = vec![TxType::Eip7702 as u8, TxType::Legacy as u8];
343 let sizes = vec![MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE];
344 let hashes = vec![
345 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
346 .unwrap(),
347 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
348 .unwrap(),
349 ];
350
351 let announcement = NewPooledTransactionHashes68 {
352 types: types.clone(),
353 sizes: sizes.clone(),
354 hashes: hashes.clone(),
355 };
356
357 let filter = EthMessageFilter::default();
358
359 let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
360 assert_eq!(outcome, FilterOutcome::Ok);
361
362 let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
363 assert_eq!(outcome, FilterOutcome::Ok);
364
365 let mut expected_data = HashMap::default();
366 expected_data.insert(hashes[0], Some((types[0], sizes[0])));
367 expected_data.insert(hashes[1], Some((types[1], sizes[1])));
368
369 assert_eq!(expected_data, valid_data.into_data());
370 }
371
372 #[test]
373 fn eth68_announcement_eip7702_tx_size_validation() {
374 let types = vec![TxType::Eip7702 as u8, TxType::Eip7702 as u8, TxType::Eip7702 as u8];
375 let sizes = vec![
377 1, MAX_MESSAGE_SIZE / 2, MAX_MESSAGE_SIZE + 1, ];
381 let hashes = vec![
382 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
383 .unwrap(),
384 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
385 .unwrap(),
386 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcccc")
387 .unwrap(),
388 ];
389
390 let announcement = NewPooledTransactionHashes68 {
391 types: types.clone(),
392 sizes: sizes.clone(),
393 hashes: hashes.clone(),
394 };
395
396 let filter = EthMessageFilter::default();
397
398 let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
399 assert_eq!(outcome, FilterOutcome::Ok);
400
401 let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
402 assert_eq!(outcome, FilterOutcome::Ok);
403
404 let mut expected_data = HashMap::default();
405
406 for i in 0..3 {
407 expected_data.insert(hashes[i], Some((types[i], sizes[i])));
408 }
409
410 assert_eq!(expected_data, valid_data.into_data());
411 }
412
413 #[test]
414 fn eth68_announcement_mixed_tx_types() {
415 let types = vec![
416 TxType::Legacy as u8,
417 TxType::Eip7702 as u8,
418 TxType::Eip1559 as u8,
419 TxType::Eip4844 as u8,
420 ];
421 let sizes = vec![MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE, MAX_MESSAGE_SIZE];
422 let hashes = vec![
423 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafa")
424 .unwrap(),
425 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefbbbb")
426 .unwrap(),
427 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcccc")
428 .unwrap(),
429 B256::from_str("0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefdddd")
430 .unwrap(),
431 ];
432
433 let announcement = NewPooledTransactionHashes68 {
434 types: types.clone(),
435 sizes: sizes.clone(),
436 hashes: hashes.clone(),
437 };
438
439 let filter = EthMessageFilter::default();
440
441 let (outcome, partially_valid_data) = filter.partially_filter_valid_entries(announcement);
442 assert_eq!(outcome, FilterOutcome::Ok);
443
444 let (outcome, valid_data) = filter.filter_valid_entries_68(partially_valid_data);
445 assert_eq!(outcome, FilterOutcome::Ok);
446
447 let mut expected_data = HashMap::default();
448 for i in 0..4 {
450 expected_data.insert(hashes[i], Some((types[i], sizes[i])));
451 }
452
453 assert_eq!(expected_data, valid_data.into_data());
454 }
455
456 #[test]
457 fn test_display_for_zst() {
458 let filter = EthMessageFilter::default();
459 assert_eq!("EthMessageFilter", &filter.to_string());
460 }
461}