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;
6use alloy_eips::{eip2718::Encodable2718, eip7594::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: Encodable2718> 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.trie_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 InMemorySize for GetCells {
131    fn size(&self) -> usize {
132        self.hashes.len() * core::mem::size_of::<B256>() + core::mem::size_of::<B128>()
133    }
134}
135
136/// The response to [`GetCells`], containing requested cells for each transaction hash.
137#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
139pub struct Cells {
140    /// Transaction hashes corresponding to the returned cell lists.
141    pub hashes: Vec<B256>,
142    /// Requested cells for each transaction hash.
143    pub cells: Vec<Vec<Cell>>,
144    /// Cell indices included in each cell list.
145    pub cell_mask: B128,
146}
147
148#[cfg(test)]
149mod tests {
150    use crate::{message::RequestPair, GetPooledTransactions, PooledTransactions};
151    use alloy_consensus::{transaction::PooledTransaction, TxEip1559, TxLegacy};
152    use alloy_primitives::{hex, Signature, TxKind, U256};
153    use alloy_rlp::{Decodable, Encodable};
154    use reth_chainspec::MIN_TRANSACTION_GAS;
155    use reth_ethereum_primitives::{Transaction, TransactionSigned};
156    use std::str::FromStr;
157
158    #[test]
159    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
160    fn encode_get_pooled_transactions() {
161        let expected = hex!(
162            "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
163        );
164        let mut data = vec![];
165        let request = RequestPair {
166            request_id: 1111,
167            message: GetPooledTransactions(vec![
168                hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
169                hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
170            ]),
171        };
172        request.encode(&mut data);
173        assert_eq!(data, expected);
174    }
175
176    #[test]
177    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
178    fn decode_get_pooled_transactions() {
179        let data = hex!(
180            "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
181        );
182        let request = RequestPair::<GetPooledTransactions>::decode(&mut &data[..]).unwrap();
183        assert_eq!(
184            request,
185            RequestPair {
186                request_id: 1111,
187                message: GetPooledTransactions(vec![
188                    hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
189                    hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
190                ])
191            }
192        );
193    }
194
195    #[test]
196    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
197    fn encode_pooled_transactions() {
198        let expected = hex!(
199            "f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"
200        );
201        let mut data = vec![];
202        let txs = vec![
203            TransactionSigned::new_unhashed(
204                Transaction::Legacy(TxLegacy {
205                    chain_id: Some(1),
206                    nonce: 0x8u64,
207                    gas_price: 0x4a817c808,
208                    gas_limit: 0x2e248,
209                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
210                    value: U256::from(0x200u64),
211                    input: Default::default(),
212                }),
213                Signature::new(
214                    U256::from_str(
215                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
216                    )
217                    .unwrap(),
218                    U256::from_str(
219                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
220                    )
221                    .unwrap(),
222                    false,
223                ),
224            ),
225            TransactionSigned::new_unhashed(
226                Transaction::Legacy(TxLegacy {
227                    chain_id: Some(1),
228                    nonce: 0x09u64,
229                    gas_price: 0x4a817c809,
230                    gas_limit: 0x33450,
231                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
232                    value: U256::from(0x2d9u64),
233                    input: Default::default(),
234                }),
235                Signature::new(
236                    U256::from_str(
237                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
238                    )
239                    .unwrap(),
240                    U256::from_str(
241                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
242                    )
243                    .unwrap(),
244                    false,
245                ),
246            ),
247        ];
248        let message: Vec<PooledTransaction> = txs
249            .into_iter()
250            .map(|tx| {
251                PooledTransaction::try_from(tx)
252                    .expect("Failed to convert TransactionSigned to PooledTransaction")
253            })
254            .collect();
255        let request = RequestPair {
256            request_id: 1111,
257            message: PooledTransactions(message), /* Assuming PooledTransactions wraps a
258                                                   * Vec<PooledTransaction> */
259        };
260        request.encode(&mut data);
261        assert_eq!(data, expected);
262    }
263
264    #[test]
265    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
266    fn decode_pooled_transactions() {
267        let data = hex!(
268            "f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"
269        );
270        let txs = vec![
271            TransactionSigned::new_unhashed(
272                Transaction::Legacy(TxLegacy {
273                    chain_id: Some(1),
274                    nonce: 0x8u64,
275                    gas_price: 0x4a817c808,
276                    gas_limit: 0x2e248,
277                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
278                    value: U256::from(0x200u64),
279                    input: Default::default(),
280                }),
281                Signature::new(
282                    U256::from_str(
283                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
284                    )
285                    .unwrap(),
286                    U256::from_str(
287                        "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
288                    )
289                    .unwrap(),
290                    false,
291                ),
292            ),
293            TransactionSigned::new_unhashed(
294                Transaction::Legacy(TxLegacy {
295                    chain_id: Some(1),
296                    nonce: 0x09u64,
297                    gas_price: 0x4a817c809,
298                    gas_limit: 0x33450,
299                    to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
300                    value: U256::from(0x2d9u64),
301                    input: Default::default(),
302                }),
303                Signature::new(
304                    U256::from_str(
305                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
306                    )
307                    .unwrap(),
308                    U256::from_str(
309                        "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
310                    )
311                    .unwrap(),
312                    false,
313                ),
314            ),
315        ];
316        let message: Vec<PooledTransaction> = txs
317            .into_iter()
318            .map(|tx| {
319                PooledTransaction::try_from(tx)
320                    .expect("Failed to convert TransactionSigned to PooledTransaction")
321            })
322            .collect();
323        let expected = RequestPair { request_id: 1111, message: PooledTransactions(message) };
324
325        let request = RequestPair::<PooledTransactions>::decode(&mut &data[..]).unwrap();
326        assert_eq!(request, expected);
327    }
328
329    #[test]
330    fn decode_pooled_transactions_network() {
331        let data = hex!(
332            "f9022980f90225f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631daf86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18"
333        );
334        let decoded_transactions =
335            RequestPair::<PooledTransactions>::decode(&mut &data[..]).unwrap();
336        let txs = vec![
337            TransactionSigned::new_unhashed(
338                Transaction::Legacy(TxLegacy {
339                    chain_id: Some(4),
340                    nonce: 15u64,
341                    gas_price: 2200000000,
342                    gas_limit: 34811,
343                    to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
344                    value: U256::from(1234u64),
345                    input: Default::default(),
346                }),
347                Signature::new(
348                    U256::from_str(
349                        "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
350                    )
351                    .unwrap(),
352                    U256::from_str(
353                        "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
354                    )
355                    .unwrap(),
356                    true,
357                ),
358            ),
359            TransactionSigned::new_unhashed(
360                Transaction::Eip1559(TxEip1559 {
361                    chain_id: 4,
362                    nonce: 26u64,
363                    max_priority_fee_per_gas: 1500000000,
364                    max_fee_per_gas: 1500000013,
365                    gas_limit: MIN_TRANSACTION_GAS,
366                    to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
367                    value: U256::from(3000000000000000000u64),
368                    input: Default::default(),
369                    access_list: Default::default(),
370                }),
371                Signature::new(
372                    U256::from_str(
373                        "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
374                    )
375                    .unwrap(),
376                    U256::from_str(
377                        "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
378                    )
379                    .unwrap(),
380                    true,
381                ),
382            ),
383            TransactionSigned::new_unhashed(
384                Transaction::Legacy(TxLegacy {
385                    chain_id: Some(4),
386                    nonce: 3u64,
387                    gas_price: 2000000000,
388                    gas_limit: 10000000,
389                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
390                    value: U256::from(1000000000000000u64),
391                    input: Default::default(),
392                }),
393                Signature::new(
394                    U256::from_str(
395                        "0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071",
396                    )
397                    .unwrap(),
398                    U256::from_str(
399                        "0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88",
400                    )
401                    .unwrap(),
402                    false,
403                ),
404            ),
405            TransactionSigned::new_unhashed(
406                Transaction::Legacy(TxLegacy {
407                    chain_id: Some(4),
408                    nonce: 1u64,
409                    gas_price: 1000000000,
410                    gas_limit: 100000,
411                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
412                    value: U256::from(693361000000000u64),
413                    input: Default::default(),
414                }),
415                Signature::new(
416                    U256::from_str(
417                        "0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a",
418                    )
419                    .unwrap(),
420                    U256::from_str(
421                        "0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da",
422                    )
423                    .unwrap(),
424                    false,
425                ),
426            ),
427            TransactionSigned::new_unhashed(
428                Transaction::Legacy(TxLegacy {
429                    chain_id: Some(4),
430                    nonce: 2u64,
431                    gas_price: 1000000000,
432                    gas_limit: 100000,
433                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
434                    value: U256::from(1000000000000000u64),
435                    input: Default::default(),
436                }),
437                Signature::new(
438                    U256::from_str(
439                        "0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae",
440                    )
441                    .unwrap(),
442                    U256::from_str(
443                        "0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18",
444                    )
445                    .unwrap(),
446                    false,
447                ),
448            ),
449        ];
450        let message: Vec<PooledTransaction> = txs
451            .into_iter()
452            .map(|tx| {
453                PooledTransaction::try_from(tx)
454                    .expect("Failed to convert TransactionSigned to PooledTransaction")
455            })
456            .collect();
457        let expected_transactions =
458            RequestPair { request_id: 0, message: PooledTransactions(message) };
459
460        // checking tx by tx for easier debugging if there are any regressions
461        for (decoded, expected) in
462            decoded_transactions.message.0.iter().zip(expected_transactions.message.0.iter())
463        {
464            assert_eq!(decoded, expected);
465        }
466
467        assert_eq!(decoded_transactions, expected_transactions);
468    }
469
470    #[test]
471    fn encode_pooled_transactions_network() {
472        let expected = hex!(
473            "f9022980f90225f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631daf86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18"
474        );
475        let txs = vec![
476            TransactionSigned::new_unhashed(
477                Transaction::Legacy(TxLegacy {
478                    chain_id: Some(4),
479                    nonce: 15u64,
480                    gas_price: 2200000000,
481                    gas_limit: 34811,
482                    to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
483                    value: U256::from(1234u64),
484                    input: Default::default(),
485                }),
486                Signature::new(
487                    U256::from_str(
488                        "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
489                    )
490                    .unwrap(),
491                    U256::from_str(
492                        "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
493                    )
494                    .unwrap(),
495                    true,
496                ),
497            ),
498            TransactionSigned::new_unhashed(
499                Transaction::Eip1559(TxEip1559 {
500                    chain_id: 4,
501                    nonce: 26u64,
502                    max_priority_fee_per_gas: 1500000000,
503                    max_fee_per_gas: 1500000013,
504                    gas_limit: MIN_TRANSACTION_GAS,
505                    to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
506                    value: U256::from(3000000000000000000u64),
507                    input: Default::default(),
508                    access_list: Default::default(),
509                }),
510                Signature::new(
511                    U256::from_str(
512                        "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
513                    )
514                    .unwrap(),
515                    U256::from_str(
516                        "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
517                    )
518                    .unwrap(),
519                    true,
520                ),
521            ),
522            TransactionSigned::new_unhashed(
523                Transaction::Legacy(TxLegacy {
524                    chain_id: Some(4),
525                    nonce: 3u64,
526                    gas_price: 2000000000,
527                    gas_limit: 10000000,
528                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
529                    value: U256::from(1000000000000000u64),
530                    input: Default::default(),
531                }),
532                Signature::new(
533                    U256::from_str(
534                        "0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071",
535                    )
536                    .unwrap(),
537                    U256::from_str(
538                        "0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88",
539                    )
540                    .unwrap(),
541                    false,
542                ),
543            ),
544            TransactionSigned::new_unhashed(
545                Transaction::Legacy(TxLegacy {
546                    chain_id: Some(4),
547                    nonce: 1u64,
548                    gas_price: 1000000000,
549                    gas_limit: 100000,
550                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
551                    value: U256::from(693361000000000u64),
552                    input: Default::default(),
553                }),
554                Signature::new(
555                    U256::from_str(
556                        "0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a",
557                    )
558                    .unwrap(),
559                    U256::from_str(
560                        "0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da",
561                    )
562                    .unwrap(),
563                    false,
564                ),
565            ),
566            TransactionSigned::new_unhashed(
567                Transaction::Legacy(TxLegacy {
568                    chain_id: Some(4),
569                    nonce: 2u64,
570                    gas_price: 1000000000,
571                    gas_limit: 100000,
572                    to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
573                    value: U256::from(1000000000000000u64),
574                    input: Default::default(),
575                }),
576                Signature::new(
577                    U256::from_str(
578                        "0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae",
579                    )
580                    .unwrap(),
581                    U256::from_str(
582                        "0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18",
583                    )
584                    .unwrap(),
585                    false,
586                ),
587            ),
588        ];
589        let message: Vec<PooledTransaction> = txs
590            .into_iter()
591            .map(|tx| {
592                PooledTransaction::try_from(tx)
593                    .expect("Failed to convert TransactionSigned to PooledTransaction")
594            })
595            .collect();
596        let transactions = RequestPair { request_id: 0, message: PooledTransactions(message) };
597
598        let mut encoded = vec![];
599        transactions.encode(&mut encoded);
600        assert_eq!(encoded.len(), transactions.length());
601        let encoded_str = hex::encode(encoded);
602        let expected_str = hex::encode(expected);
603        assert_eq!(encoded_str.len(), expected_str.len());
604        assert_eq!(encoded_str, expected_str);
605    }
606}