1use crate::{EthMessage, EthVersion, NetworkPrimitives};
4use alloc::{sync::Arc, vec::Vec};
5use alloy_consensus::transaction::TxHashRef;
6use alloy_eips::eip2718::Typed2718;
7use alloy_primitives::{bytes::BufMut, Bytes, TxHash, B128, B256, U128};
8use alloy_rlp::{
9 decode_append, Decodable, Encodable, Header, RlpDecodable, RlpDecodableWrapper, RlpEncodable,
10 RlpEncodableWrapper,
11};
12use core::{fmt::Debug, mem};
13use derive_more::{Deref, DerefMut, IntoIterator};
14use reth_codecs_derive::{add_arbitrary_tests, generate_tests};
15use reth_ethereum_primitives::TransactionSigned;
16use reth_primitives_traits::{sync::OnceLock, Block, InMemorySize, SignedTransaction};
17
18pub const SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE: usize = 4096;
25
26#[derive(
28 Clone,
29 Debug,
30 PartialEq,
31 Eq,
32 RlpEncodableWrapper,
33 RlpDecodableWrapper,
34 Default,
35 Deref,
36 DerefMut,
37 IntoIterator,
38)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
41#[add_arbitrary_tests(rlp)]
42pub struct NewBlockHashes(
43 pub Vec<BlockHashNumber>,
46);
47
48impl NewBlockHashes {
51 pub fn latest(&self) -> Option<&BlockHashNumber> {
53 self.iter().max_by_key(|b| b.number)
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
61#[add_arbitrary_tests(rlp)]
62pub struct BlockHashNumber {
63 pub hash: B256,
65 pub number: u64,
67}
68
69impl From<Vec<BlockHashNumber>> for NewBlockHashes {
70 fn from(v: Vec<BlockHashNumber>) -> Self {
71 Self(v)
72 }
73}
74
75impl From<NewBlockHashes> for Vec<BlockHashNumber> {
76 fn from(v: NewBlockHashes) -> Self {
77 v.0
78 }
79}
80
81pub trait NewBlockPayload:
83 Encodable + Decodable + Clone + Eq + Debug + Send + Sync + Unpin + 'static
84{
85 type Block: Block;
87
88 fn block(&self) -> &Self::Block;
90}
91
92#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
97pub struct NewBlock<B = reth_ethereum_primitives::Block> {
98 pub block: B,
100 pub td: U128,
102}
103
104impl<B: Block + 'static> NewBlockPayload for NewBlock<B> {
105 type Block = B;
106
107 fn block(&self) -> &Self::Block {
108 &self.block
109 }
110}
111
112generate_tests!(#[rlp, 25] NewBlock<reth_ethereum_primitives::Block>, EthNewBlockTests);
113
114#[derive(
117 Clone,
118 Debug,
119 PartialEq,
120 Eq,
121 RlpEncodableWrapper,
122 RlpDecodableWrapper,
123 Default,
124 Deref,
125 IntoIterator,
126)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
129#[add_arbitrary_tests(rlp, 10)]
130pub struct Transactions<T = TransactionSigned>(
131 pub Vec<T>,
133);
134
135impl<T: SignedTransaction> Transactions<T> {
136 pub fn has_eip4844(&self) -> bool {
138 self.iter().any(|tx| tx.is_eip4844())
139 }
140}
141
142impl<T> From<Vec<T>> for Transactions<T> {
143 fn from(txs: Vec<T>) -> Self {
144 Self(txs)
145 }
146}
147
148impl<T> From<Transactions<T>> for Vec<T> {
149 fn from(txs: Transactions<T>) -> Self {
150 txs.0
151 }
152}
153
154impl<T: Decodable + InMemorySize> Transactions<T> {
155 pub fn decode_with_memory_budget(
159 buf: &mut &[u8],
160 memory_budget: usize,
161 ) -> alloy_rlp::Result<Self> {
162 decode_list_with_memory_budget(buf, memory_budget).map(Self)
163 }
164}
165
166pub fn decode_list_with_memory_budget<T: Decodable + InMemorySize>(
169 buf: &mut &[u8],
170 memory_budget: usize,
171) -> alloy_rlp::Result<Vec<T>> {
172 let Header { list, payload_length } = Header::decode(buf)?;
173 if !list {
174 return Err(alloy_rlp::Error::UnexpectedString)
175 }
176 let (mut payload, rest) = buf.split_at(payload_length);
178
179 let mut txs = Vec::with_capacity(estimated_transaction_list_capacity(payload_length));
180 let mut total_size = 0usize;
181
182 while !payload.is_empty() {
183 let item = T::decode(&mut payload)?;
184 total_size = total_size.saturating_add(item.size());
185
186 if total_size > memory_budget {
187 break;
188 }
189
190 txs.push(item);
191 }
192
193 *buf = rest;
194 Ok(txs)
195}
196
197const MIN_TRANSACTION_RLP_SIZE_ESTIMATE: usize = 128;
200const MIN_PREALLOCATED_TRANSACTIONS: usize = 4;
201const MAX_PREALLOCATED_TRANSACTIONS: usize = 1024;
202
203const fn estimated_transaction_list_capacity(payload_length: usize) -> usize {
204 let estimate = payload_length / MIN_TRANSACTION_RLP_SIZE_ESTIMATE;
205 if estimate < MIN_PREALLOCATED_TRANSACTIONS {
206 0
207 } else if estimate > MAX_PREALLOCATED_TRANSACTIONS {
208 MAX_PREALLOCATED_TRANSACTIONS
209 } else {
210 estimate
211 }
212}
213
214#[derive(
219 Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Deref, IntoIterator,
220)]
221#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
222#[add_arbitrary_tests(rlp, 20)]
223pub struct SharedTransactions<T = TransactionSigned>(
224 pub Vec<Arc<T>>,
226);
227
228pub trait BroadcastPoolTransaction:
230 Encodable + TxHashRef + Typed2718 + Send + Sync + 'static
231{
232}
233
234impl<T> BroadcastPoolTransaction for T where
235 T: Encodable + TxHashRef + Typed2718 + Send + Sync + 'static
236{
237}
238
239pub struct LazyEncoded<T: ?Sized> {
244 value: Arc<T>,
245 encoded: Arc<OnceLock<Bytes>>,
246}
247
248impl<T: ?Sized> Clone for LazyEncoded<T> {
249 fn clone(&self) -> Self {
250 Self { value: Arc::clone(&self.value), encoded: Arc::clone(&self.encoded) }
251 }
252}
253
254impl LazyEncoded<dyn BroadcastPoolTransaction> {
255 pub fn new<T>(value: T) -> Self
257 where
258 T: BroadcastPoolTransaction,
259 {
260 let value: Arc<dyn BroadcastPoolTransaction> = Arc::new(value);
261 Self { value, encoded: Arc::new(OnceLock::new()) }
262 }
263}
264
265impl<T: Encodable + ?Sized> Encodable for LazyEncoded<T> {
266 fn encode(&self, out: &mut dyn BufMut) {
267 let encoded = self.encoded.get_or_init(|| self.encode_uncached());
268 out.put_slice(encoded);
269 }
270
271 fn length(&self) -> usize {
272 self.encoded.get_or_init(|| self.encode_uncached()).len()
273 }
274}
275
276impl<T: Encodable + ?Sized> LazyEncoded<T> {
277 fn encode_uncached(&self) -> Bytes {
278 alloy_rlp::encode(&self.value).into()
279 }
280}
281
282impl<T: TxHashRef + ?Sized> TxHashRef for LazyEncoded<T> {
283 fn tx_hash(&self) -> &TxHash {
284 self.value.tx_hash()
285 }
286}
287
288impl<T: Typed2718 + ?Sized> Typed2718 for LazyEncoded<T> {
289 fn ty(&self) -> u8 {
290 self.value.ty()
291 }
292}
293
294impl<T: ?Sized> Debug for LazyEncoded<T> {
295 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
296 f.debug_struct("LazyEncoded")
297 .field("is_cached", &self.encoded.get().is_some())
298 .finish_non_exhaustive()
299 }
300}
301
302pub type LazyEncodedTransaction = LazyEncoded<dyn BroadcastPoolTransaction>;
304
305#[derive(Clone, Debug, Deref, RlpEncodableWrapper)]
313pub struct BroadcastPoolTransactions(pub Vec<LazyEncodedTransaction>);
314
315impl BroadcastPoolTransactions {
316 pub fn iter_hashes(&self) -> impl Iterator<Item = &TxHash> + '_ {
318 self.0.iter().map(TxHashRef::tx_hash)
319 }
320}
321
322#[derive(Clone, Debug, PartialEq, Eq)]
324#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
325pub enum NewPooledTransactionHashes {
326 Eth66(NewPooledTransactionHashes66),
328 Eth68(NewPooledTransactionHashes68),
332 Eth72(NewPooledTransactionHashes72),
339}
340
341impl NewPooledTransactionHashes {
344 pub const fn version(&self) -> EthVersion {
346 match self {
347 Self::Eth66(_) => EthVersion::Eth66,
348 Self::Eth68(_) => EthVersion::Eth68,
349 Self::Eth72(_) => EthVersion::Eth72,
350 }
351 }
352
353 pub const fn is_valid_for_version(&self, version: EthVersion) -> bool {
355 match self {
356 Self::Eth66(_) => {
357 matches!(version, EthVersion::Eth67 | EthVersion::Eth66)
358 }
359 Self::Eth68(_) => {
360 matches!(
361 version,
362 EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71
363 )
364 }
365 Self::Eth72(_) => {
366 matches!(version, EthVersion::Eth72)
367 }
368 }
369 }
370
371 pub fn iter_hashes(&self) -> impl Iterator<Item = &B256> + '_ {
373 match self {
374 Self::Eth66(msg) => msg.iter(),
375 Self::Eth68(msg) => msg.hashes.iter(),
376 Self::Eth72(msg) => msg.hashes.iter(),
377 }
378 }
379
380 pub const fn hashes(&self) -> &Vec<B256> {
382 match self {
383 Self::Eth66(msg) => &msg.0,
384 Self::Eth68(msg) => &msg.hashes,
385 Self::Eth72(msg) => &msg.hashes,
386 }
387 }
388
389 pub const fn hashes_mut(&mut self) -> &mut Vec<B256> {
391 match self {
392 Self::Eth66(msg) => &mut msg.0,
393 Self::Eth68(msg) => &mut msg.hashes,
394 Self::Eth72(msg) => &mut msg.hashes,
395 }
396 }
397
398 pub fn into_hashes(self) -> Vec<B256> {
400 match self {
401 Self::Eth66(msg) => msg.0,
402 Self::Eth68(msg) => msg.hashes,
403 Self::Eth72(msg) => msg.hashes,
404 }
405 }
406
407 pub fn into_iter_hashes(self) -> impl Iterator<Item = B256> {
409 match self {
410 Self::Eth66(msg) => msg.into_iter(),
411 Self::Eth68(msg) => msg.hashes.into_iter(),
412 Self::Eth72(msg) => msg.hashes.into_iter(),
413 }
414 }
415
416 pub fn truncate(&mut self, len: usize) {
419 match self {
420 Self::Eth66(msg) => msg.truncate(len),
421 Self::Eth68(msg) => {
422 msg.types.truncate(len);
423 msg.sizes.truncate(len);
424 msg.hashes.truncate(len);
425 }
426 Self::Eth72(msg) => {
427 msg.types.truncate(len);
428 msg.sizes.truncate(len);
429 msg.hashes.truncate(len);
430 }
431 }
432 }
433
434 pub const fn is_empty(&self) -> bool {
436 match self {
437 Self::Eth66(msg) => msg.0.is_empty(),
438 Self::Eth68(msg) => msg.hashes.is_empty(),
439 Self::Eth72(msg) => msg.hashes.is_empty(),
440 }
441 }
442
443 pub const fn len(&self) -> usize {
445 match self {
446 Self::Eth66(msg) => msg.0.len(),
447 Self::Eth68(msg) => msg.hashes.len(),
448 Self::Eth72(msg) => msg.hashes.len(),
449 }
450 }
451
452 pub const fn as_eth72(&self) -> Option<&NewPooledTransactionHashes72> {
454 match self {
455 Self::Eth66(_) | Self::Eth68(_) => None,
456 Self::Eth72(msg) => Some(msg),
457 }
458 }
459
460 pub const fn as_eth72_mut(&mut self) -> Option<&mut NewPooledTransactionHashes72> {
462 match self {
463 Self::Eth66(_) | Self::Eth68(_) => None,
464 Self::Eth72(msg) => Some(msg),
465 }
466 }
467
468 pub const fn as_eth68(&self) -> Option<&NewPooledTransactionHashes68> {
470 match self {
471 Self::Eth66(_) | Self::Eth72(_) => None,
472 Self::Eth68(msg) => Some(msg),
473 }
474 }
475
476 pub const fn as_eth68_mut(&mut self) -> Option<&mut NewPooledTransactionHashes68> {
478 match self {
479 Self::Eth66(_) | Self::Eth72(_) => None,
480 Self::Eth68(msg) => Some(msg),
481 }
482 }
483
484 pub const fn as_eth66_mut(&mut self) -> Option<&mut NewPooledTransactionHashes66> {
486 match self {
487 Self::Eth66(msg) => Some(msg),
488 Self::Eth68(_) | Self::Eth72(_) => None,
489 }
490 }
491
492 pub fn take_eth68(&mut self) -> Option<NewPooledTransactionHashes68> {
494 match self {
495 Self::Eth66(_) | Self::Eth72(_) => None,
496 Self::Eth68(msg) => Some(mem::take(msg)),
497 }
498 }
499
500 pub fn take_eth66(&mut self) -> Option<NewPooledTransactionHashes66> {
502 match self {
503 Self::Eth66(msg) => Some(mem::take(msg)),
504 Self::Eth68(_) | Self::Eth72(_) => None,
505 }
506 }
507}
508
509impl<N: NetworkPrimitives> From<NewPooledTransactionHashes> for EthMessage<N> {
510 fn from(value: NewPooledTransactionHashes) -> Self {
511 match value {
512 NewPooledTransactionHashes::Eth66(msg) => Self::NewPooledTransactionHashes66(msg),
513 NewPooledTransactionHashes::Eth68(msg) => Self::NewPooledTransactionHashes68(msg),
514 NewPooledTransactionHashes::Eth72(msg) => Self::NewPooledTransactionHashes72(msg),
515 }
516 }
517}
518
519impl From<NewPooledTransactionHashes66> for NewPooledTransactionHashes {
520 fn from(hashes: NewPooledTransactionHashes66) -> Self {
521 Self::Eth66(hashes)
522 }
523}
524
525impl From<NewPooledTransactionHashes68> for NewPooledTransactionHashes {
526 fn from(hashes: NewPooledTransactionHashes68) -> Self {
527 Self::Eth68(hashes)
528 }
529}
530
531impl From<NewPooledTransactionHashes72> for NewPooledTransactionHashes {
532 fn from(hashes: NewPooledTransactionHashes72) -> Self {
533 Self::Eth72(hashes)
534 }
535}
536
537#[derive(
540 Clone,
541 Debug,
542 PartialEq,
543 Eq,
544 RlpEncodableWrapper,
545 RlpDecodableWrapper,
546 Default,
547 Deref,
548 DerefMut,
549 IntoIterator,
550)]
551#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
552#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
553#[add_arbitrary_tests(rlp)]
554pub struct NewPooledTransactionHashes66(
555 pub Vec<B256>,
559);
560
561impl NewPooledTransactionHashes66 {
562 pub fn with_capacity(capacity: usize) -> Self {
564 Self(Vec::with_capacity(capacity))
565 }
566}
567
568impl From<Vec<B256>> for NewPooledTransactionHashes66 {
569 fn from(v: Vec<B256>) -> Self {
570 Self(v)
571 }
572}
573
574#[derive(Clone, Debug, PartialEq, Eq, Default)]
577#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
578pub struct NewPooledTransactionHashes68 {
579 pub types: Vec<u8>,
603 pub sizes: Vec<usize>,
605 pub hashes: Vec<B256>,
607}
608
609#[cfg(feature = "arbitrary")]
610impl proptest::prelude::Arbitrary for NewPooledTransactionHashes68 {
611 type Parameters = ();
612 fn arbitrary_with(_args: ()) -> Self::Strategy {
613 use proptest::{collection::vec, prelude::*};
614 let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); vec_length
618 .prop_flat_map(|len| {
619 let types_vec = vec(
621 proptest_arbitrary_interop::arb::<reth_ethereum_primitives::TxType>()
622 .prop_map(|ty| ty as u8),
623 len..=len,
624 );
625
626 let sizes_vec = vec(proptest::num::usize::ANY.prop_map(|x| x % 131072), len..=len);
628 let hashes_vec = vec(any::<B256>(), len..=len);
629
630 (types_vec, sizes_vec, hashes_vec)
631 })
632 .prop_map(|(types, sizes, hashes)| Self { types, sizes, hashes })
633 .boxed()
634 }
635
636 type Strategy = proptest::prelude::BoxedStrategy<Self>;
637}
638
639impl NewPooledTransactionHashes68 {
640 pub const fn len(&self) -> usize {
642 self.hashes.len()
643 }
644
645 pub const fn is_empty(&self) -> bool {
647 self.hashes.is_empty()
648 }
649
650 pub fn with_capacity(capacity: usize) -> Self {
652 Self {
653 types: Vec::with_capacity(capacity),
654 sizes: Vec::with_capacity(capacity),
655 hashes: Vec::with_capacity(capacity),
656 }
657 }
658
659 pub fn metadata_iter(&self) -> impl Iterator<Item = (&B256, (u8, usize))> {
661 self.hashes.iter().zip(self.types.iter().copied().zip(self.sizes.iter().copied()))
662 }
663
664 pub fn push<T: SignedTransaction>(&mut self, tx: &T) {
666 self.hashes.push(*tx.tx_hash());
667 self.sizes.push(tx.encode_2718_len());
668 self.types.push(tx.ty());
669 }
670
671 pub fn extend<'a, T: SignedTransaction>(&mut self, txs: impl IntoIterator<Item = &'a T>) {
673 for tx in txs {
674 self.push(tx);
675 }
676 }
677
678 pub fn shrink_to_fit(&mut self) {
680 self.hashes.shrink_to_fit();
681 self.sizes.shrink_to_fit();
682 self.types.shrink_to_fit()
683 }
684
685 pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
687 self.push(tx);
688 self
689 }
690
691 pub fn with_transactions<'a, T: SignedTransaction>(
693 mut self,
694 txs: impl IntoIterator<Item = &'a T>,
695 ) -> Self {
696 self.extend(txs);
697 self
698 }
699}
700
701impl Encodable for NewPooledTransactionHashes68 {
702 fn encode(&self, out: &mut dyn bytes::BufMut) {
703 #[derive(RlpEncodable)]
704 struct EncodableNewPooledTransactionHashes68<'a> {
705 types: &'a [u8],
706 sizes: &'a Vec<usize>,
707 hashes: &'a Vec<B256>,
708 }
709
710 let encodable = EncodableNewPooledTransactionHashes68 {
711 types: &self.types[..],
712 sizes: &self.sizes,
713 hashes: &self.hashes,
714 };
715
716 encodable.encode(out);
717 }
718 fn length(&self) -> usize {
719 #[derive(RlpEncodable)]
720 struct EncodableNewPooledTransactionHashes68<'a> {
721 types: &'a [u8],
722 sizes: &'a Vec<usize>,
723 hashes: &'a Vec<B256>,
724 }
725
726 let encodable = EncodableNewPooledTransactionHashes68 {
727 types: &self.types[..],
728 sizes: &self.sizes,
729 hashes: &self.hashes,
730 };
731
732 encodable.length()
733 }
734}
735
736impl Decodable for NewPooledTransactionHashes68 {
737 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
738 let Header { list, payload_length } = Header::decode(buf)?;
739 if !list {
740 return Err(alloy_rlp::Error::UnexpectedString)
741 }
742 let (mut payload, rest) = buf.split_at(payload_length);
744 let (types, sizes, hashes) = decode_pooled_transaction_hashes_payload(&mut payload)?;
745
746 if !payload.is_empty() {
747 return Err(alloy_rlp::Error::ListLengthMismatch {
748 expected: payload_length,
749 got: payload_length - payload.len(),
750 })
751 }
752
753 ensure_pooled_transaction_hashes_lengths(hashes.len(), types.len(), sizes.len())?;
754
755 *buf = rest;
756 Ok(Self { types, sizes, hashes })
757 }
758}
759
760#[derive(Clone, Debug, PartialEq, Eq, Default)]
763#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
764pub struct NewPooledTransactionHashes72 {
765 pub types: Vec<u8>,
774 pub sizes: Vec<usize>,
776 pub hashes: Vec<B256>,
778 pub cell_mask: Option<B128>,
790}
791
792#[cfg(feature = "arbitrary")]
793impl proptest::prelude::Arbitrary for NewPooledTransactionHashes72 {
794 type Parameters = ();
795 fn arbitrary_with(_args: ()) -> Self::Strategy {
796 use proptest::{collection::vec, prelude::*};
797 let vec_length = any::<usize>().prop_map(|x| x % 100 + 1); vec_length
801 .prop_flat_map(|len| {
802 let types_vec = vec(
804 proptest_arbitrary_interop::arb::<reth_ethereum_primitives::TxType>()
805 .prop_map(|ty| ty as u8),
806 len..=len,
807 );
808
809 let sizes_vec = vec(proptest::num::usize::ANY.prop_map(|x| x % 131072), len..=len);
811 let hashes_vec = vec(any::<B256>(), len..=len);
812 let cell_mask =
815 any::<Option<B128>>().prop_map(|mask| mask.filter(|mask| !mask.is_zero()));
816
817 (types_vec, sizes_vec, hashes_vec, cell_mask)
818 })
819 .prop_map(|(types, sizes, hashes, cell_mask)| Self { types, sizes, hashes, cell_mask })
820 .boxed()
821 }
822
823 type Strategy = proptest::prelude::BoxedStrategy<Self>;
824}
825
826impl NewPooledTransactionHashes72 {
827 pub const fn len(&self) -> usize {
829 self.hashes.len()
830 }
831
832 pub const fn is_empty(&self) -> bool {
834 self.hashes.is_empty()
835 }
836
837 pub const ALL_CELLS_MASK: B128 = B128::repeat_byte(0xff);
842
843 pub fn with_capacity(capacity: usize) -> Self {
845 Self {
846 types: Vec::with_capacity(capacity),
847 sizes: Vec::with_capacity(capacity),
848 hashes: Vec::with_capacity(capacity),
849 cell_mask: None,
850 }
851 }
852
853 pub fn metadata_iter(&self) -> impl Iterator<Item = (&B256, (u8, usize))> {
855 self.hashes.iter().zip(self.types.iter().copied().zip(self.sizes.iter().copied()))
856 }
857
858 pub fn push<T: SignedTransaction>(&mut self, tx: &T) {
860 self.hashes.push(*tx.tx_hash());
861 self.sizes.push(tx.encode_2718_len());
862 self.types.push(tx.ty());
863 if tx.is_eip4844() {
864 self.cell_mask = Some(Self::ALL_CELLS_MASK);
865 }
866 }
867
868 pub fn extend<'a, T: SignedTransaction>(&mut self, txs: impl IntoIterator<Item = &'a T>) {
870 for tx in txs {
871 self.push(tx);
872 }
873 }
874
875 pub fn shrink_to_fit(&mut self) {
877 self.hashes.shrink_to_fit();
878 self.sizes.shrink_to_fit();
879 self.types.shrink_to_fit()
880 }
881
882 pub fn with_transaction<T: SignedTransaction>(mut self, tx: &T) -> Self {
884 self.push(tx);
885 self
886 }
887
888 pub fn with_transactions<'a, T: SignedTransaction>(
890 mut self,
891 txs: impl IntoIterator<Item = &'a T>,
892 ) -> Self {
893 self.extend(txs);
894 self
895 }
896
897 fn payload_length(&self) -> usize {
898 self.types.as_slice().length() +
899 self.sizes.length() +
900 self.hashes.length() +
901 self.cell_mask.unwrap_or_default().length()
902 }
903}
904
905impl Encodable for NewPooledTransactionHashes72 {
906 fn encode(&self, out: &mut dyn bytes::BufMut) {
907 Header { list: true, payload_length: self.payload_length() }.encode(out);
908 self.types.as_slice().encode(out);
909 self.sizes.encode(out);
910 self.hashes.encode(out);
911 self.cell_mask.unwrap_or_default().encode(out);
913 }
914
915 fn length(&self) -> usize {
916 Header { list: true, payload_length: self.payload_length() }.length_with_payload()
917 }
918}
919
920impl Decodable for NewPooledTransactionHashes72 {
921 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
922 let Header { list, payload_length } = Header::decode(buf)?;
923 if !list {
924 return Err(alloy_rlp::Error::UnexpectedString)
925 }
926 let (mut payload, rest) = buf.split_at(payload_length);
928 let (types, sizes, hashes) = decode_pooled_transaction_hashes_payload(&mut payload)?;
929 let Some(first_byte) = payload.first().copied() else {
930 return Err(alloy_rlp::Error::InputTooShort)
931 };
932 let cell_mask = if first_byte == alloy_rlp::EMPTY_STRING_CODE {
933 payload = &payload[1..];
935 None
936 } else {
937 Some(B128::decode(&mut payload)?).filter(|mask| !mask.is_zero())
940 };
941
942 if !payload.is_empty() {
943 return Err(alloy_rlp::Error::ListLengthMismatch {
944 expected: payload_length,
945 got: payload_length - payload.len(),
946 })
947 }
948
949 ensure_pooled_transaction_hashes_lengths(hashes.len(), types.len(), sizes.len())?;
950
951 *buf = rest;
952 Ok(Self { types, sizes, hashes, cell_mask })
953 }
954}
955
956const NEW_POOLED_TRANSACTION_HASHES_DECODE_CAP: usize =
961 2 * SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE;
962
963#[inline]
964fn decode_pooled_transaction_hashes_payload(
965 payload: &mut &[u8],
966) -> alloy_rlp::Result<(Vec<u8>, Vec<usize>, Vec<B256>)> {
967 let types = Bytes::decode(payload)?;
968 let capacity = types.len().min(NEW_POOLED_TRANSACTION_HASHES_DECODE_CAP);
969
970 let mut sizes = Vec::with_capacity(capacity);
971 decode_append(payload, &mut sizes)?;
972
973 let mut hashes = Vec::with_capacity(capacity);
974 decode_append(payload, &mut hashes)?;
975
976 Ok((types.into(), sizes, hashes))
977}
978
979#[inline]
980const fn ensure_pooled_transaction_hashes_lengths(
981 hashes_len: usize,
982 types_len: usize,
983 sizes_len: usize,
984) -> alloy_rlp::Result<()> {
985 if hashes_len != types_len {
986 return Err(alloy_rlp::Error::ListLengthMismatch { expected: hashes_len, got: types_len })
987 }
988 if hashes_len != sizes_len {
989 return Err(alloy_rlp::Error::ListLengthMismatch { expected: hashes_len, got: sizes_len })
990 }
991
992 Ok(())
993}
994
995pub trait HandleMempoolData {
998 fn is_empty(&self) -> bool;
1000
1001 fn len(&self) -> usize;
1003
1004 fn retain_by_hash(&mut self, f: impl FnMut(&TxHash) -> bool);
1006}
1007
1008impl<T: SignedTransaction> HandleMempoolData for Vec<T> {
1009 fn is_empty(&self) -> bool {
1010 self.is_empty()
1011 }
1012
1013 fn len(&self) -> usize {
1014 self.len()
1015 }
1016
1017 fn retain_by_hash(&mut self, mut f: impl FnMut(&TxHash) -> bool) {
1018 self.retain(|tx| f(tx.tx_hash()))
1019 }
1020}
1021
1022#[derive(Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
1025#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1026#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1027pub struct BlockRangeUpdate {
1028 pub earliest: u64,
1030 pub latest: u64,
1032 pub latest_hash: B256,
1034}
1035
1036impl InMemorySize for NewPooledTransactionHashes {
1037 fn size(&self) -> usize {
1038 match self {
1039 Self::Eth66(msg) => msg.0.len() * core::mem::size_of::<B256>(),
1040 Self::Eth68(msg) => {
1041 msg.types.len() * core::mem::size_of::<u8>() +
1042 msg.sizes.len() * core::mem::size_of::<usize>() +
1043 msg.hashes.len() * core::mem::size_of::<B256>()
1044 }
1045 Self::Eth72(msg) => {
1046 msg.types.len() * core::mem::size_of::<u8>() +
1047 msg.sizes.len() * core::mem::size_of::<usize>() +
1048 msg.hashes.len() * core::mem::size_of::<B256>() +
1049 core::mem::size_of::<B128>()
1050 }
1051 }
1052 }
1053}
1054
1055#[cfg(test)]
1056mod tests {
1057 use super::*;
1058 use alloy_consensus::{transaction::TxHashRef, Typed2718};
1059 use alloy_eips::eip2718::Encodable2718;
1060 use alloy_primitives::{hex, Bytes, Signature, U256};
1061 use alloy_rlp::{RlpDecodable, RlpEncodable};
1062 use proptest::prelude::*;
1063 use reth_ethereum_primitives::{Transaction, TransactionSigned};
1064 use std::str::FromStr;
1065
1066 fn test_encoding_vector<T: Encodable + Decodable + PartialEq + core::fmt::Debug>(
1069 input: (T, &[u8]),
1070 ) {
1071 let (expected_decoded, expected_encoded) = input;
1072 let mut encoded = Vec::new();
1073 expected_decoded.encode(&mut encoded);
1074
1075 assert_eq!(hex::encode(&encoded), hex::encode(expected_encoded));
1076
1077 let decoded = T::decode(&mut encoded.as_ref()).unwrap();
1078 assert_eq!(expected_decoded, decoded);
1079 }
1080
1081 fn encoded<T: Encodable>(value: &T) -> Vec<u8> {
1082 let mut out = Vec::new();
1083 value.encode(&mut out);
1084 out
1085 }
1086
1087 #[derive(RlpEncodable, RlpDecodable)]
1088 struct EncodableNewPooledTransactionHashes68 {
1089 types: Bytes,
1090 sizes: Vec<usize>,
1091 hashes: Vec<B256>,
1092 }
1093
1094 type NewPooledTransactionHashes68Fields = (Vec<u8>, Vec<usize>, Vec<B256>);
1095
1096 fn decode_eth68_hashes_derived(
1097 buf: &mut &[u8],
1098 ) -> alloy_rlp::Result<NewPooledTransactionHashes68> {
1099 let encodable = EncodableNewPooledTransactionHashes68::decode(buf)?;
1100 let msg = NewPooledTransactionHashes68 {
1101 types: encodable.types.into(),
1102 sizes: encodable.sizes,
1103 hashes: encodable.hashes,
1104 };
1105
1106 ensure_pooled_transaction_hashes_lengths(
1107 msg.hashes.len(),
1108 msg.types.len(),
1109 msg.sizes.len(),
1110 )?;
1111
1112 Ok(msg)
1113 }
1114
1115 fn eth68_hash_fields_strategy() -> impl Strategy<Value = NewPooledTransactionHashes68Fields> {
1116 (0usize..128, 0usize..128, 0usize..128).prop_flat_map(
1117 |(types_len, sizes_len, hashes_len)| {
1118 (
1119 proptest::collection::vec(any::<u8>(), types_len),
1120 proptest::collection::vec(0usize..131_072, sizes_len),
1121 proptest::collection::vec(any::<B256>(), hashes_len),
1122 )
1123 },
1124 )
1125 }
1126
1127 proptest! {
1128 #[test]
1129 fn broadcast_pool_transactions_match_shared_transactions_encoding(
1130 txs in proptest::collection::vec(
1131 proptest_arbitrary_interop::arb::<TransactionSigned>(),
1132 0..32,
1133 )
1134 ) {
1135 let shared = SharedTransactions::<TransactionSigned>(
1136 txs.iter().cloned().map(Arc::new).collect(),
1137 );
1138 let broadcast = BroadcastPoolTransactions(
1139 txs.iter().cloned().map(LazyEncoded::new).collect(),
1140 );
1141
1142 prop_assert_eq!(broadcast.length(), shared.length());
1143
1144 let shared_encoded = encoded(&shared);
1145 let broadcast_encoded = encoded(&broadcast);
1146 prop_assert_eq!(&broadcast_encoded, &shared_encoded);
1147
1148 let broadcast_encoded_cached = encoded(&broadcast);
1149 prop_assert_eq!(&broadcast_encoded_cached, &shared_encoded);
1150
1151 let mut shared_bytes = shared_encoded.as_slice();
1152 let decoded_shared = SharedTransactions::<TransactionSigned>::decode(&mut shared_bytes)
1153 .expect("shared transactions decode");
1154 prop_assert!(shared_bytes.is_empty());
1155
1156 let mut broadcast_bytes = broadcast_encoded.as_slice();
1157 let decoded_broadcast =
1158 SharedTransactions::<TransactionSigned>::decode(&mut broadcast_bytes)
1159 .expect("broadcast pool transactions decode as shared transactions");
1160 prop_assert!(broadcast_bytes.is_empty());
1161
1162 prop_assert_eq!(decoded_broadcast, decoded_shared);
1163 }
1164
1165 #[test]
1166 fn eth_68_handrolled_decode_matches_derived_implementation(
1167 (types, sizes, hashes) in eth68_hash_fields_strategy()
1168 ) {
1169 let encodable = EncodableNewPooledTransactionHashes68 {
1170 types: Bytes::from(types),
1171 sizes,
1172 hashes,
1173 };
1174 let encoded = encoded(&encodable);
1175
1176 let mut derived_buf = encoded.as_slice();
1177 let derived = decode_eth68_hashes_derived(&mut derived_buf);
1178
1179 let mut handrolled_buf = encoded.as_slice();
1180 let handrolled = NewPooledTransactionHashes68::decode(&mut handrolled_buf);
1181
1182 let handrolled_is_ok = handrolled.is_ok();
1183 prop_assert_eq!(&handrolled, &derived);
1184 if handrolled_is_ok {
1185 prop_assert!(derived_buf.is_empty());
1186 prop_assert!(handrolled_buf.is_empty());
1187 }
1188 }
1189 }
1190
1191 #[test]
1192 fn decode_error_preserves_payload_position() {
1193 let encoded = [0xc1, 0x80, 0xaa];
1194
1195 let mut input = encoded.as_slice();
1196 assert!(
1197 decode_list_with_memory_budget::<TransactionSigned>(&mut input, usize::MAX).is_err()
1198 );
1199 assert_eq!(input, &encoded[1..]);
1200
1201 let mut input = encoded.as_slice();
1202 assert!(NewPooledTransactionHashes68::decode(&mut input).is_err());
1203 assert_eq!(input, &encoded[1..]);
1204
1205 let mut input = encoded.as_slice();
1206 assert!(NewPooledTransactionHashes72::decode(&mut input).is_err());
1207 assert_eq!(input, &encoded[1..]);
1208 }
1209
1210 #[test]
1211 fn can_return_latest_block() {
1212 let mut blocks = NewBlockHashes(vec![BlockHashNumber { hash: B256::random(), number: 0 }]);
1213 let latest = blocks.latest().unwrap();
1214 assert_eq!(latest.number, 0);
1215
1216 blocks.push(BlockHashNumber { hash: B256::random(), number: 100 });
1217 blocks.push(BlockHashNumber { hash: B256::random(), number: 2 });
1218 let latest = blocks.latest().unwrap();
1219 assert_eq!(latest.number, 100);
1220 }
1221
1222 #[test]
1223 fn eth_68_tx_hash_roundtrip() {
1224 let vectors = vec![
1225 (
1226 NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] },
1227 &hex!("c380c0c0")[..],
1228 ),
1229 (
1230 NewPooledTransactionHashes68 {
1231 types: vec![0x00],
1232 sizes: vec![0x00],
1233 hashes: vec![
1234 B256::from_str(
1235 "0x0000000000000000000000000000000000000000000000000000000000000000",
1236 )
1237 .unwrap(),
1238 ],
1239 },
1240 &hex!(
1241 "e500c180e1a00000000000000000000000000000000000000000000000000000000000000000"
1242 )[..],
1243 ),
1244 (
1245 NewPooledTransactionHashes68 {
1246 types: vec![0x00, 0x00],
1247 sizes: vec![0x00, 0x00],
1248 hashes: vec![
1249 B256::from_str(
1250 "0x0000000000000000000000000000000000000000000000000000000000000000",
1251 )
1252 .unwrap(),
1253 B256::from_str(
1254 "0x0000000000000000000000000000000000000000000000000000000000000000",
1255 )
1256 .unwrap(),
1257 ],
1258 },
1259 &hex!(
1260 "f84a820000c28080f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"
1261 )[..],
1262 ),
1263 (
1264 NewPooledTransactionHashes68 {
1265 types: vec![0x02],
1266 sizes: vec![0xb6],
1267 hashes: vec![
1268 B256::from_str(
1269 "0xfecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124",
1270 )
1271 .unwrap(),
1272 ],
1273 },
1274 &hex!(
1275 "e602c281b6e1a0fecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124"
1276 )[..],
1277 ),
1278 (
1279 NewPooledTransactionHashes68 {
1280 types: vec![0xff, 0xff],
1281 sizes: vec![0xffffffff, 0xffffffff],
1282 hashes: vec![
1283 B256::from_str(
1284 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1285 )
1286 .unwrap(),
1287 B256::from_str(
1288 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1289 )
1290 .unwrap(),
1291 ],
1292 },
1293 &hex!(
1294 "f85282ffffca84ffffffff84fffffffff842a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1295 )[..],
1296 ),
1297 (
1298 NewPooledTransactionHashes68 {
1299 types: vec![0xff, 0xff],
1300 sizes: vec![0xffffffff, 0xffffffff],
1301 hashes: vec![
1302 B256::from_str(
1303 "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe",
1304 )
1305 .unwrap(),
1306 B256::from_str(
1307 "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe",
1308 )
1309 .unwrap(),
1310 ],
1311 },
1312 &hex!(
1313 "f85282ffffca84ffffffff84fffffffff842a0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafea0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe"
1314 )[..],
1315 ),
1316 (
1317 NewPooledTransactionHashes68 {
1318 types: vec![0x10, 0x10],
1319 sizes: vec![0xdeadc0de, 0xdeadc0de],
1320 hashes: vec![
1321 B256::from_str(
1322 "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2",
1323 )
1324 .unwrap(),
1325 B256::from_str(
1326 "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2",
1327 )
1328 .unwrap(),
1329 ],
1330 },
1331 &hex!(
1332 "f852821010ca84deadc0de84deadc0def842a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2"
1333 )[..],
1334 ),
1335 (
1336 NewPooledTransactionHashes68 {
1337 types: vec![0x6f, 0x6f],
1338 sizes: vec![0x7fffffff, 0x7fffffff],
1339 hashes: vec![
1340 B256::from_str(
1341 "0x0000000000000000000000000000000000000000000000000000000000000002",
1342 )
1343 .unwrap(),
1344 B256::from_str(
1345 "0x0000000000000000000000000000000000000000000000000000000000000002",
1346 )
1347 .unwrap(),
1348 ],
1349 },
1350 &hex!(
1351 "f852826f6fca847fffffff847ffffffff842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000002"
1352 )[..],
1353 ),
1354 ];
1355
1356 for vector in vectors {
1357 test_encoding_vector(vector);
1358 }
1359 }
1360
1361 #[test]
1362 fn eth_72_zero_cell_mask_decodes_as_none() {
1363 let zero = NewPooledTransactionHashes72 {
1366 types: vec![],
1367 sizes: vec![],
1368 hashes: vec![],
1369 cell_mask: Some(B128::ZERO),
1370 };
1371
1372 let mut encoded = Vec::new();
1373 zero.encode(&mut encoded);
1374
1375 let decoded = NewPooledTransactionHashes72::decode(&mut &encoded[..]).unwrap();
1376
1377 assert_eq!(decoded.cell_mask, None);
1378 assert_eq!(encoded, hex!("d480c0c09000000000000000000000000000000000"));
1379 }
1380
1381 #[test]
1382 fn eth_72_tx_hash_roundtrip() {
1383 let vectors = vec![
1384 (
1387 NewPooledTransactionHashes72 {
1388 types: vec![],
1389 sizes: vec![],
1390 hashes: vec![],
1391 cell_mask: None,
1392 },
1393 &hex!("d480c0c09000000000000000000000000000000000")[..],
1394 ),
1395 (
1396 NewPooledTransactionHashes72 {
1397 types: vec![],
1398 sizes: vec![],
1399 hashes: vec![],
1400 cell_mask: Some(B128::repeat_byte(0x11)),
1401 },
1402 &hex!("d480c0c09011111111111111111111111111111111")[..],
1403 ),
1404 ];
1405
1406 for vector in vectors {
1407 test_encoding_vector(vector);
1408 }
1409 }
1410
1411 #[test]
1412 fn eth_72_decodes_spec_nil_cell_mask() {
1413 let encoded = hex!("c480c0c080");
1416
1417 let decoded = NewPooledTransactionHashes72::decode(&mut encoded.as_ref()).unwrap();
1418
1419 assert_eq!(decoded.cell_mask, None);
1420 }
1421
1422 #[test]
1423 fn eth_72_rejects_missing_cell_mask() {
1424 let encoded_eth68_payload = hex!("c380c0c0");
1425
1426 let result = NewPooledTransactionHashes72::decode(&mut encoded_eth68_payload.as_ref());
1427
1428 assert!(matches!(result, Err(alloy_rlp::Error::InputTooShort)));
1429 }
1430
1431 fn signed_transaction() -> impl SignedTransaction {
1432 TransactionSigned::new_unhashed(
1433 Transaction::Legacy(Default::default()),
1434 Signature::new(
1435 U256::from_str(
1436 "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
1437 )
1438 .unwrap(),
1439 U256::from_str(
1440 "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
1441 )
1442 .unwrap(),
1443 false,
1444 ),
1445 )
1446 }
1447
1448 #[test]
1449 fn test_pooled_tx_hashes_68_push() {
1450 let tx = signed_transaction();
1451 let mut tx_hashes =
1452 NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] };
1453 tx_hashes.push(&tx);
1454 assert_eq!(tx_hashes.types.len(), 1);
1455 assert_eq!(tx_hashes.sizes.len(), 1);
1456 assert_eq!(tx_hashes.hashes.len(), 1);
1457 assert_eq!(tx_hashes.types[0], tx.ty());
1458 assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1459 assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1460 }
1461
1462 #[test]
1463 fn test_pooled_tx_hashes_68_extend() {
1464 let tx = signed_transaction();
1465 let txs = vec![tx.clone(), tx.clone()];
1466 let mut tx_hashes =
1467 NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] };
1468 tx_hashes.extend(&txs);
1469 assert_eq!(tx_hashes.types.len(), 2);
1470 assert_eq!(tx_hashes.sizes.len(), 2);
1471 assert_eq!(tx_hashes.hashes.len(), 2);
1472 assert_eq!(tx_hashes.types[0], tx.ty());
1473 assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1474 assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1475 assert_eq!(tx_hashes.types[1], tx.ty());
1476 assert_eq!(tx_hashes.sizes[1], tx.encode_2718_len());
1477 assert_eq!(tx_hashes.hashes[1], *tx.tx_hash());
1478 }
1479
1480 #[test]
1481 fn test_pooled_tx_hashes_68_with_transaction() {
1482 let tx = signed_transaction();
1483 let tx_hashes =
1484 NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] }
1485 .with_transaction(&tx);
1486 assert_eq!(tx_hashes.types.len(), 1);
1487 assert_eq!(tx_hashes.sizes.len(), 1);
1488 assert_eq!(tx_hashes.hashes.len(), 1);
1489 assert_eq!(tx_hashes.types[0], tx.ty());
1490 assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1491 assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1492 }
1493
1494 #[test]
1495 fn test_pooled_tx_hashes_68_with_transactions() {
1496 let tx = signed_transaction();
1497 let txs = vec![tx.clone(), tx.clone()];
1498 let tx_hashes =
1499 NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] }
1500 .with_transactions(&txs);
1501 assert_eq!(tx_hashes.types.len(), 2);
1502 assert_eq!(tx_hashes.sizes.len(), 2);
1503 assert_eq!(tx_hashes.hashes.len(), 2);
1504 assert_eq!(tx_hashes.types[0], tx.ty());
1505 assert_eq!(tx_hashes.sizes[0], tx.encode_2718_len());
1506 assert_eq!(tx_hashes.hashes[0], *tx.tx_hash());
1507 assert_eq!(tx_hashes.types[1], tx.ty());
1508 assert_eq!(tx_hashes.sizes[1], tx.encode_2718_len());
1509 assert_eq!(tx_hashes.hashes[1], *tx.tx_hash());
1510 }
1511}