1use 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
22pub const SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE: usize = 4096;
29
30#[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 pub Vec<BlockHashNumber>,
50);
51
52impl NewBlockHashes {
55 pub fn latest(&self) -> Option<&BlockHashNumber> {
57 self.iter().max_by_key(|b| b.number)
58 }
59}
60
61#[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 pub hash: B256,
69 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
85pub trait NewBlockPayload:
87 Encodable + Decodable + Clone + Eq + Debug + Send + Sync + Unpin + 'static
88{
89 type Block: Block;
91
92 fn block(&self) -> &Self::Block;
94}
95
96#[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 pub block: B,
104 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#[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 pub Vec<T>,
137);
138
139impl<T: SignedTransaction> Transactions<T> {
140 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 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
170pub 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
205const 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#[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 pub Vec<Arc<T>>,
234);
235
236pub 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
247pub 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 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
312pub type LazyEncodedTransaction = LazyEncoded<dyn BroadcastPoolTransaction>;
314
315#[derive(Clone, Debug, Deref)]
323pub struct BroadcastPoolTransactions(pub Vec<LazyEncodedTransaction>);
324
325impl BroadcastPoolTransactions {
326 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#[derive(Clone, Debug, PartialEq, Eq)]
344#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
345pub enum NewPooledTransactionHashes {
346 Eth66(NewPooledTransactionHashes66),
348 Eth68(NewPooledTransactionHashes68),
352 Eth72(NewPooledTransactionHashes72),
359}
360
361impl NewPooledTransactionHashes {
364 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[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 pub Vec<B256>,
579);
580
581impl NewPooledTransactionHashes66 {
582 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#[derive(Clone, Debug, PartialEq, Eq, Default)]
597#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
598pub struct NewPooledTransactionHashes68 {
599 pub types: Vec<u8>,
623 pub sizes: Vec<usize>,
625 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 let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); vec_length
638 .prop_flat_map(|len| {
639 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 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 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 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 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 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 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 pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
697 self.push(tx);
698 self
699 }
700
701 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#[derive(Clone, Debug, PartialEq, Eq, Default)]
778#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
779pub struct NewPooledTransactionHashes72 {
780 pub types: Vec<u8>,
789 pub sizes: Vec<usize>,
791 pub hashes: Vec<B256>,
793 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 let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); vec_length
816 .prop_flat_map(|len| {
817 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 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 pub const ALL_CELLS_MASK: B128 = B128::repeat_byte(0xff);
844
845 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 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 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 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 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 pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
886 self.push(tx);
887 self
888 }
889
890 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 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 payload = &payload[1..];
940 None
941 } else {
942 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
962const 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
1001pub trait DedupPayload {
1003 type Value;
1005
1006 fn is_empty(&self) -> bool;
1008
1009 fn len(&self) -> usize;
1011
1012 fn dedup(self) -> PartiallyValidData<Self::Value>;
1014}
1015
1016pub 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
1117pub trait HandleMempoolData {
1120 fn is_empty(&self) -> bool;
1122
1123 fn len(&self) -> usize;
1125
1126 fn retain_by_hash(&mut self, f: impl FnMut(&TxHash) -> bool);
1128}
1129
1130pub trait HandleVersionedMempoolData {
1132 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#[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 cell_mask: Option<B128>,
1180}
1181
1182handle_mempool_data_map_impl!(PartiallyValidData<V>, <V>);
1183
1184impl<V> PartiallyValidData<V> {
1185 pub const fn from_raw_data(data: B256Map<V>, version: Option<EthVersion>) -> Self {
1187 Self { data, version, cell_mask: None }
1188 }
1189
1190 pub const fn from_raw_data_eth72(data: B256Map<V>) -> Self {
1192 Self::from_raw_data(data, Some(EthVersion::Eth72))
1193 }
1194
1195 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 pub const fn from_raw_data_eth68(data: B256Map<V>) -> Self {
1205 Self::from_raw_data(data, Some(EthVersion::Eth68))
1206 }
1207
1208 pub const fn from_raw_data_eth66(data: B256Map<V>) -> Self {
1210 Self::from_raw_data(data, Some(EthVersion::Eth66))
1211 }
1212
1213 pub fn empty_eth72() -> Self {
1216 Self::from_raw_data_eth72(B256Map::default())
1217 }
1218
1219 pub fn empty_eth68() -> Self {
1222 Self::from_raw_data_eth68(B256Map::default())
1223 }
1224
1225 pub fn empty_eth66() -> Self {
1228 Self::from_raw_data_eth66(B256Map::default())
1229 }
1230
1231 pub const fn msg_version(&self) -> Option<EthVersion> {
1234 self.version
1235 }
1236
1237 pub const fn eth72_cell_mask(&self) -> Option<B128> {
1239 self.cell_mask
1240 }
1241
1242 pub fn into_data(self) -> B256Map<V> {
1244 self.data
1245 }
1246}
1247
1248#[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 cell_mask: Option<B128>,
1259}
1260
1261handle_mempool_data_map_impl!(ValidAnnouncementData,);
1262
1263impl ValidAnnouncementData {
1264 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 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 pub const fn eth72_cell_mask(&self) -> Option<B128> {
1285 self.cell_mask
1286 }
1287
1288 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#[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 pub fn with_capacity(capacity: usize) -> Self {
1315 Self::new(B256Set::with_capacity_and_hasher(capacity, Default::default()))
1316 }
1317
1318 fn empty() -> Self {
1320 Self::new(B256Set::default())
1321 }
1322
1323 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#[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 pub earliest: u64,
1360 pub latest: u64,
1362 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 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 (
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 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}