Skip to main content

reth_eth_wire_types/
transactions.rs

1//! Implements the `GetPooledTransactions` and `PooledTransactions` message types.
2
3use crate::broadcast::decode_list_with_memory_budget;
4use alloc::vec::Vec;
5use alloy_consensus::transaction::{PooledTransaction, TxHashRef};
6use alloy_eips::eip7594::{BlobCellMask, Cell};
7use alloy_primitives::{B128, B256};
8use alloy_rlp::{Decodable, RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
9use derive_more::{Constructor, Deref, IntoIterator};
10use reth_codecs_derive::add_arbitrary_tests;
11use reth_primitives_traits::InMemorySize;
12
13/// A list of transaction hashes that the peer would like transaction bodies for.
14#[derive(
15    Clone,
16    Debug,
17    PartialEq,
18    Eq,
19    RlpEncodableWrapper,
20    RlpDecodableWrapper,
21    Default,
22    Deref,
23    IntoIterator,
24)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
27#[add_arbitrary_tests(rlp)]
28pub struct GetPooledTransactions(
29    /// The transaction hashes to request transaction bodies for.
30    pub Vec<B256>,
31);
32
33impl<T> From<Vec<T>> for GetPooledTransactions
34where
35    T: Into<B256>,
36{
37    fn from(hashes: Vec<T>) -> Self {
38        Self(hashes.into_iter().map(|h| h.into()).collect())
39    }
40}
41
42impl InMemorySize for GetPooledTransactions {
43    fn size(&self) -> usize {
44        self.0.len() * core::mem::size_of::<B256>()
45    }
46}
47
48/// The response to [`GetPooledTransactions`], containing the transaction bodies associated with
49/// the requested hashes.
50///
51/// This response may not contain all bodies requested, but the bodies should be in the same order
52/// as the request's hashes. Hashes may be skipped, and the client should ensure that each body
53/// corresponds to a requested hash. Hashes may need to be re-requested if the bodies are not
54/// included in the response.
55// #[derive_arbitrary(rlp, 10)]
56#[derive(
57    Clone,
58    Debug,
59    PartialEq,
60    Eq,
61    RlpEncodableWrapper,
62    RlpDecodableWrapper,
63    IntoIterator,
64    Deref,
65    Constructor,
66)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68pub struct PooledTransactions<T = PooledTransaction>(
69    /// The transaction bodies, each of which should correspond to a requested hash.
70    pub Vec<T>,
71);
72
73impl<T: Decodable + InMemorySize> PooledTransactions<T> {
74    /// Decodes the RLP list of transactions, stopping once the cumulative
75    /// [`InMemorySize`] of decoded transactions exceeds `memory_budget` bytes.
76    /// Any remaining transactions in the payload are skipped.
77    pub fn decode_with_memory_budget(
78        buf: &mut &[u8],
79        memory_budget: usize,
80    ) -> alloy_rlp::Result<Self> {
81        decode_list_with_memory_budget(buf, memory_budget).map(Self)
82    }
83}
84
85impl<T: TxHashRef> PooledTransactions<T> {
86    /// Returns an iterator over the transaction hashes in this response.
87    pub fn hashes(&self) -> impl Iterator<Item = B256> + '_ {
88        self.iter().map(|tx| *tx.tx_hash())
89    }
90}
91
92impl<T, U> TryFrom<Vec<U>> for PooledTransactions<T>
93where
94    T: TryFrom<U>,
95{
96    type Error = T::Error;
97
98    fn try_from(txs: Vec<U>) -> Result<Self, Self::Error> {
99        txs.into_iter().map(T::try_from).collect()
100    }
101}
102
103impl<T> FromIterator<T> for PooledTransactions<T> {
104    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
105        Self(iter.into_iter().collect())
106    }
107}
108
109impl<T> Default for PooledTransactions<T> {
110    fn default() -> Self {
111        Self(Default::default())
112    }
113}
114
115/// A list of transaction hashes and the cell indices requested for each transaction.
116///
117/// See [EIP-8070]: Sparse Blobpool
118///
119/// [EIP-8070]: https://eips.ethereum.org/EIPS/eip-8070
120#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122pub struct GetCells {
123    /// Transaction hashes to request cells for.
124    pub hashes: Vec<B256>,
125    /// Requested cell indices, encoded with the same syntax as the `cell_mask` in
126    /// `NewPooledTransactionHashes`.
127    pub cell_mask: B128,
128}
129
130impl GetCells {
131    /// Returns the requested cell indices, decoded from the little-endian wire bitvector.
132    pub fn cell_mask(&self) -> BlobCellMask {
133        BlobCellMask::from_bits(u128::from_le_bytes(self.cell_mask.into()))
134    }
135}
136
137impl InMemorySize for GetCells {
138    fn size(&self) -> usize {
139        self.hashes.len() * core::mem::size_of::<B256>() + core::mem::size_of::<B128>()
140    }
141}
142
143/// The response to [`GetCells`], containing requested cells for each transaction hash.
144#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146pub struct Cells {
147    /// Transaction hashes corresponding to the returned cell lists.
148    pub hashes: Vec<B256>,
149    /// Requested cells for each transaction hash.
150    pub cells: Vec<Vec<Cell>>,
151    /// Cell indices included in each cell list.
152    pub cell_mask: B128,
153}
154
155#[cfg(test)]
156mod tests {
157    use crate::{message::RequestPair, GetPooledTransactions, PooledTransactions};
158    use alloy_consensus::{transaction::PooledTransaction, TxEip1559, TxLegacy};
159    use alloy_primitives::{hex, Signature, TxKind, U256};
160    use alloy_rlp::{Decodable, Encodable};
161    use reth_chainspec::MIN_TRANSACTION_GAS;
162    use reth_ethereum_primitives::{Transaction, TransactionSigned};
163    use std::str::FromStr;
164
165    #[test]
166    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
167    fn encode_get_pooled_transactions() {
168        let expected = hex!(
169            "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
170        );
171        let mut data = vec![];
172        let request = RequestPair {
173            request_id: 1111,
174            message: GetPooledTransactions(vec![
175                hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
176                hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
177            ]),
178        };
179        request.encode(&mut data);
180        assert_eq!(data, expected);
181    }
182
183    #[test]
184    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
185    fn decode_get_pooled_transactions() {
186        let data = hex!(
187            "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
188        );
189        let request = RequestPair::<GetPooledTransactions>::decode(&mut &data[..]).unwrap();
190        assert_eq!(
191            request,
192            RequestPair {
193                request_id: 1111,
194                message: GetPooledTransactions(vec![
195                    hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
196                    hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
197                ])
198            }
199        );
200    }
201
202    #[test]
203    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
204    fn encode_pooled_transactions() {
205        let expected = hex!(
206            "f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"
207        );
208        let mut data = vec![];
209        let txs = vec![
210            TransactionSigned::new_unhashed(
211                Transaction::Legacy(TxLegacy {
212                    chain_id: Some(1),
213                    nonce: 0x8u64,
214                    gas_price: 0x4a817c808,
215                    gas_limit: 0x2e248,
216                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
217                    value: U256::from(0x200u64),
218                    input: Default::default(),
219                }),
220                Signature::new(
221                    U256::from_str(
222                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
223                    )
224                    .unwrap(),
225                    U256::from_str(
226                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
227                    )
228                    .unwrap(),
229                    false,
230                ),
231            ),
232            TransactionSigned::new_unhashed(
233                Transaction::Legacy(TxLegacy {
234                    chain_id: Some(1),
235                    nonce: 0x09u64,
236                    gas_price: 0x4a817c809,
237                    gas_limit: 0x33450,
238                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
239                    value: U256::from(0x2d9u64),
240                    input: Default::default(),
241                }),
242                Signature::new(
243                    U256::from_str(
244                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
245                    )
246                    .unwrap(),
247                    U256::from_str(
248                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
249                    )
250                    .unwrap(),
251                    false,
252                ),
253            ),
254        ];
255        let message: Vec<PooledTransaction> = txs
256            .into_iter()
257            .map(|tx| {
258                PooledTransaction::try_from(tx)
259                    .expect("Failed to convert TransactionSigned to PooledTransaction")
260            })
261            .collect();
262        let request = RequestPair {
263            request_id: 1111,
264            message: PooledTransactions(message), /* Assuming PooledTransactions wraps a
265                                                   * Vec<PooledTransaction> */
266        };
267        request.encode(&mut data);
268        assert_eq!(data, expected);
269    }
270
271    #[test]
272    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
273    fn decode_pooled_transactions() {
274        let data = hex!(
275            "f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"
276        );
277        let txs = vec![
278            TransactionSigned::new_unhashed(
279                Transaction::Legacy(TxLegacy {
280                    chain_id: Some(1),
281                    nonce: 0x8u64,
282                    gas_price: 0x4a817c808,
283                    gas_limit: 0x2e248,
284                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
285                    value: U256::from(0x200u64),
286                    input: Default::default(),
287                }),
288                Signature::new(
289                    U256::from_str(
290                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
291                    )
292                    .unwrap(),
293                    U256::from_str(
294                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
295                    )
296                    .unwrap(),
297                    false,
298                ),
299            ),
300            TransactionSigned::new_unhashed(
301                Transaction::Legacy(TxLegacy {
302                    chain_id: Some(1),
303                    nonce: 0x09u64,
304                    gas_price: 0x4a817c809,
305                    gas_limit: 0x33450,
306                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
307                    value: U256::from(0x2d9u64),
308                    input: Default::default(),
309                }),
310                Signature::new(
311                    U256::from_str(
312                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
313                    )
314                    .unwrap(),
315                    U256::from_str(
316                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
317                    )
318                    .unwrap(),
319                    false,
320                ),
321            ),
322        ];
323        let message: Vec<PooledTransaction> = txs
324            .into_iter()
325            .map(|tx| {
326                PooledTransaction::try_from(tx)
327                    .expect("Failed to convert TransactionSigned to PooledTransaction")
328            })
329            .collect();
330        let expected = RequestPair { request_id: 1111, message: PooledTransactions(message) };
331
332        let request = RequestPair::<PooledTransactions>::decode(&mut &data[..]).unwrap();
333        assert_eq!(request, expected);
334    }
335
336    #[test]
337    fn decode_pooled_transactions_network() {
338        let data = hex!(
339            "f9022980f90225f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631daf86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18"
340        );
341        let decoded_transactions =
342            RequestPair::<PooledTransactions>::decode(&mut &data[..]).unwrap();
343        let txs = vec![
344            TransactionSigned::new_unhashed(
345                Transaction::Legacy(TxLegacy {
346                    chain_id: Some(4),
347                    nonce: 15u64,
348                    gas_price: 2200000000,
349                    gas_limit: 34811,
350                    to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
351                    value: U256::from(1234u64),
352                    input: Default::default(),
353                }),
354                Signature::new(
355                    U256::from_str(
356                        "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
357                    )
358                    .unwrap(),
359                    U256::from_str(
360                        "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
361                    )
362                    .unwrap(),
363                    true,
364                ),
365            ),
366            TransactionSigned::new_unhashed(
367                Transaction::Eip1559(TxEip1559 {
368                    chain_id: 4,
369                    nonce: 26u64,
370                    max_priority_fee_per_gas: 1500000000,
371                    max_fee_per_gas: 1500000013,
372                    gas_limit: MIN_TRANSACTION_GAS,
373                    to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
374                    value: U256::from(3000000000000000000u64),
375                    input: Default::default(),
376                    access_list: Default::default(),
377                }),
378                Signature::new(
379                    U256::from_str(
380                        "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
381                    )
382                    .unwrap(),
383                    U256::from_str(
384                        "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
385                    )
386                    .unwrap(),
387                    true,
388                ),
389            ),
390            TransactionSigned::new_unhashed(
391                Transaction::Legacy(TxLegacy {
392                    chain_id: Some(4),
393                    nonce: 3u64,
394                    gas_price: 2000000000,
395                    gas_limit: 10000000,
396                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
397                    value: U256::from(1000000000000000u64),
398                    input: Default::default(),
399                }),
400                Signature::new(
401                    U256::from_str(
402                        "0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071",
403                    )
404                    .unwrap(),
405                    U256::from_str(
406                        "0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88",
407                    )
408                    .unwrap(),
409                    false,
410                ),
411            ),
412            TransactionSigned::new_unhashed(
413                Transaction::Legacy(TxLegacy {
414                    chain_id: Some(4),
415                    nonce: 1u64,
416                    gas_price: 1000000000,
417                    gas_limit: 100000,
418                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
419                    value: U256::from(693361000000000u64),
420                    input: Default::default(),
421                }),
422                Signature::new(
423                    U256::from_str(
424                        "0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a",
425                    )
426                    .unwrap(),
427                    U256::from_str(
428                        "0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da",
429                    )
430                    .unwrap(),
431                    false,
432                ),
433            ),
434            TransactionSigned::new_unhashed(
435                Transaction::Legacy(TxLegacy {
436                    chain_id: Some(4),
437                    nonce: 2u64,
438                    gas_price: 1000000000,
439                    gas_limit: 100000,
440                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
441                    value: U256::from(1000000000000000u64),
442                    input: Default::default(),
443                }),
444                Signature::new(
445                    U256::from_str(
446                        "0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae",
447                    )
448                    .unwrap(),
449                    U256::from_str(
450                        "0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18",
451                    )
452                    .unwrap(),
453                    false,
454                ),
455            ),
456        ];
457        let message: Vec<PooledTransaction> = txs
458            .into_iter()
459            .map(|tx| {
460                PooledTransaction::try_from(tx)
461                    .expect("Failed to convert TransactionSigned to PooledTransaction")
462            })
463            .collect();
464        let expected_transactions =
465            RequestPair { request_id: 0, message: PooledTransactions(message) };
466
467        // checking tx by tx for easier debugging if there are any regressions
468        for (decoded, expected) in
469            decoded_transactions.message.0.iter().zip(expected_transactions.message.0.iter())
470        {
471            assert_eq!(decoded, expected);
472        }
473
474        assert_eq!(decoded_transactions, expected_transactions);
475    }
476
477    #[test]
478    fn encode_pooled_transactions_network() {
479        let expected = hex!(
480            "f9022980f90225f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631daf86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18"
481        );
482        let txs = vec![
483            TransactionSigned::new_unhashed(
484                Transaction::Legacy(TxLegacy {
485                    chain_id: Some(4),
486                    nonce: 15u64,
487                    gas_price: 2200000000,
488                    gas_limit: 34811,
489                    to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
490                    value: U256::from(1234u64),
491                    input: Default::default(),
492                }),
493                Signature::new(
494                    U256::from_str(
495                        "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
496                    )
497                    .unwrap(),
498                    U256::from_str(
499                        "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
500                    )
501                    .unwrap(),
502                    true,
503                ),
504            ),
505            TransactionSigned::new_unhashed(
506                Transaction::Eip1559(TxEip1559 {
507                    chain_id: 4,
508                    nonce: 26u64,
509                    max_priority_fee_per_gas: 1500000000,
510                    max_fee_per_gas: 1500000013,
511                    gas_limit: MIN_TRANSACTION_GAS,
512                    to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
513                    value: U256::from(3000000000000000000u64),
514                    input: Default::default(),
515                    access_list: Default::default(),
516                }),
517                Signature::new(
518                    U256::from_str(
519                        "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
520                    )
521                    .unwrap(),
522                    U256::from_str(
523                        "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
524                    )
525                    .unwrap(),
526                    true,
527                ),
528            ),
529            TransactionSigned::new_unhashed(
530                Transaction::Legacy(TxLegacy {
531                    chain_id: Some(4),
532                    nonce: 3u64,
533                    gas_price: 2000000000,
534                    gas_limit: 10000000,
535                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
536                    value: U256::from(1000000000000000u64),
537                    input: Default::default(),
538                }),
539                Signature::new(
540                    U256::from_str(
541                        "0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071",
542                    )
543                    .unwrap(),
544                    U256::from_str(
545                        "0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88",
546                    )
547                    .unwrap(),
548                    false,
549                ),
550            ),
551            TransactionSigned::new_unhashed(
552                Transaction::Legacy(TxLegacy {
553                    chain_id: Some(4),
554                    nonce: 1u64,
555                    gas_price: 1000000000,
556                    gas_limit: 100000,
557                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
558                    value: U256::from(693361000000000u64),
559                    input: Default::default(),
560                }),
561                Signature::new(
562                    U256::from_str(
563                        "0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a",
564                    )
565                    .unwrap(),
566                    U256::from_str(
567                        "0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da",
568                    )
569                    .unwrap(),
570                    false,
571                ),
572            ),
573            TransactionSigned::new_unhashed(
574                Transaction::Legacy(TxLegacy {
575                    chain_id: Some(4),
576                    nonce: 2u64,
577                    gas_price: 1000000000,
578                    gas_limit: 100000,
579                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
580                    value: U256::from(1000000000000000u64),
581                    input: Default::default(),
582                }),
583                Signature::new(
584                    U256::from_str(
585                        "0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae",
586                    )
587                    .unwrap(),
588                    U256::from_str(
589                        "0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18",
590                    )
591                    .unwrap(),
592                    false,
593                ),
594            ),
595        ];
596        let message: Vec<PooledTransaction> = txs
597            .into_iter()
598            .map(|tx| {
599                PooledTransaction::try_from(tx)
600                    .expect("Failed to convert TransactionSigned to PooledTransaction")
601            })
602            .collect();
603        let transactions = RequestPair { request_id: 0, message: PooledTransactions(message) };
604
605        let mut encoded = vec![];
606        transactions.encode(&mut encoded);
607        assert_eq!(encoded.len(), transactions.length());
608        let encoded_str = hex::encode(encoded);
609        let expected_str = hex::encode(expected);
610        assert_eq!(encoded_str.len(), expected_str.len());
611        assert_eq!(encoded_str, expected_str);
612    }
613}