1use alloc::vec::Vec;
4use alloy_consensus::transaction::PooledTransaction;
5use alloy_eips::eip2718::Encodable2718;
6use alloy_primitives::B256;
7use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
8use derive_more::{Constructor, Deref, IntoIterator};
9use reth_codecs_derive::add_arbitrary_tests;
10
11#[derive(
13 Clone,
14 Debug,
15 PartialEq,
16 Eq,
17 RlpEncodableWrapper,
18 RlpDecodableWrapper,
19 Default,
20 Deref,
21 IntoIterator,
22)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
25#[add_arbitrary_tests(rlp)]
26pub struct GetPooledTransactions(
27 pub Vec<B256>,
29);
30
31impl<T> From<Vec<T>> for GetPooledTransactions
32where
33 T: Into<B256>,
34{
35 fn from(hashes: Vec<T>) -> Self {
36 Self(hashes.into_iter().map(|h| h.into()).collect())
37 }
38}
39
40#[derive(
49 Clone,
50 Debug,
51 PartialEq,
52 Eq,
53 RlpEncodableWrapper,
54 RlpDecodableWrapper,
55 IntoIterator,
56 Deref,
57 Constructor,
58)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60pub struct PooledTransactions<T = PooledTransaction>(
61 pub Vec<T>,
63);
64
65impl<T: Encodable2718> PooledTransactions<T> {
66 pub fn hashes(&self) -> impl Iterator<Item = B256> + '_ {
68 self.iter().map(|tx| tx.trie_hash())
69 }
70}
71
72impl<T, U> TryFrom<Vec<U>> for PooledTransactions<T>
73where
74 T: TryFrom<U>,
75{
76 type Error = T::Error;
77
78 fn try_from(txs: Vec<U>) -> Result<Self, Self::Error> {
79 txs.into_iter().map(T::try_from).collect()
80 }
81}
82
83impl<T> FromIterator<T> for PooledTransactions<T> {
84 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
85 Self(iter.into_iter().collect())
86 }
87}
88
89impl<T> Default for PooledTransactions<T> {
90 fn default() -> Self {
91 Self(Default::default())
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use crate::{message::RequestPair, GetPooledTransactions, PooledTransactions};
98 use alloy_consensus::{transaction::PooledTransaction, TxEip1559, TxLegacy};
99 use alloy_primitives::{hex, Signature, TxKind, U256};
100 use alloy_rlp::{Decodable, Encodable};
101 use reth_chainspec::MIN_TRANSACTION_GAS;
102 use reth_ethereum_primitives::{Transaction, TransactionSigned};
103 use std::str::FromStr;
104
105 #[test]
106 fn encode_get_pooled_transactions() {
108 let expected = hex!(
109 "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
110 );
111 let mut data = vec![];
112 let request = RequestPair {
113 request_id: 1111,
114 message: GetPooledTransactions(vec![
115 hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
116 hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
117 ]),
118 };
119 request.encode(&mut data);
120 assert_eq!(data, expected);
121 }
122
123 #[test]
124 fn decode_get_pooled_transactions() {
126 let data = hex!(
127 "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
128 );
129 let request = RequestPair::<GetPooledTransactions>::decode(&mut &data[..]).unwrap();
130 assert_eq!(
131 request,
132 RequestPair {
133 request_id: 1111,
134 message: GetPooledTransactions(vec![
135 hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
136 hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
137 ])
138 }
139 );
140 }
141
142 #[test]
143 fn encode_pooled_transactions() {
145 let expected = hex!(
146 "f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"
147 );
148 let mut data = vec![];
149 let txs = vec![
150 TransactionSigned::new_unhashed(
151 Transaction::Legacy(TxLegacy {
152 chain_id: Some(1),
153 nonce: 0x8u64,
154 gas_price: 0x4a817c808,
155 gas_limit: 0x2e248,
156 to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
157 value: U256::from(0x200u64),
158 input: Default::default(),
159 }),
160 Signature::new(
161 U256::from_str(
162 "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
163 )
164 .unwrap(),
165 U256::from_str(
166 "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
167 )
168 .unwrap(),
169 false,
170 ),
171 ),
172 TransactionSigned::new_unhashed(
173 Transaction::Legacy(TxLegacy {
174 chain_id: Some(1),
175 nonce: 0x09u64,
176 gas_price: 0x4a817c809,
177 gas_limit: 0x33450,
178 to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
179 value: U256::from(0x2d9u64),
180 input: Default::default(),
181 }),
182 Signature::new(
183 U256::from_str(
184 "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
185 )
186 .unwrap(),
187 U256::from_str(
188 "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
189 )
190 .unwrap(),
191 false,
192 ),
193 ),
194 ];
195 let message: Vec<PooledTransaction> = txs
196 .into_iter()
197 .map(|tx| {
198 PooledTransaction::try_from(tx)
199 .expect("Failed to convert TransactionSigned to PooledTransaction")
200 })
201 .collect();
202 let request = RequestPair {
203 request_id: 1111,
204 message: PooledTransactions(message), };
207 request.encode(&mut data);
208 assert_eq!(data, expected);
209 }
210
211 #[test]
212 fn decode_pooled_transactions() {
214 let data = hex!(
215 "f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"
216 );
217 let txs = vec![
218 TransactionSigned::new_unhashed(
219 Transaction::Legacy(TxLegacy {
220 chain_id: Some(1),
221 nonce: 0x8u64,
222 gas_price: 0x4a817c808,
223 gas_limit: 0x2e248,
224 to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
225 value: U256::from(0x200u64),
226 input: Default::default(),
227 }),
228 Signature::new(
229 U256::from_str(
230 "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12",
231 )
232 .unwrap(),
233 U256::from_str(
234 "0x64b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
235 )
236 .unwrap(),
237 false,
238 ),
239 ),
240 TransactionSigned::new_unhashed(
241 Transaction::Legacy(TxLegacy {
242 chain_id: Some(1),
243 nonce: 0x09u64,
244 gas_price: 0x4a817c809,
245 gas_limit: 0x33450,
246 to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
247 value: U256::from(0x2d9u64),
248 input: Default::default(),
249 }),
250 Signature::new(
251 U256::from_str(
252 "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
253 )
254 .unwrap(),
255 U256::from_str(
256 "0x52f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
257 )
258 .unwrap(),
259 false,
260 ),
261 ),
262 ];
263 let message: Vec<PooledTransaction> = txs
264 .into_iter()
265 .map(|tx| {
266 PooledTransaction::try_from(tx)
267 .expect("Failed to convert TransactionSigned to PooledTransaction")
268 })
269 .collect();
270 let expected = RequestPair { request_id: 1111, message: PooledTransactions(message) };
271
272 let request = RequestPair::<PooledTransactions>::decode(&mut &data[..]).unwrap();
273 assert_eq!(request, expected);
274 }
275
276 #[test]
277 fn decode_pooled_transactions_network() {
278 let data = hex!(
279 "f9022980f90225f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631daf86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18"
280 );
281 let decoded_transactions =
282 RequestPair::<PooledTransactions>::decode(&mut &data[..]).unwrap();
283 let txs = vec![
284 TransactionSigned::new_unhashed(
285 Transaction::Legacy(TxLegacy {
286 chain_id: Some(4),
287 nonce: 15u64,
288 gas_price: 2200000000,
289 gas_limit: 34811,
290 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
291 value: U256::from(1234u64),
292 input: Default::default(),
293 }),
294 Signature::new(
295 U256::from_str(
296 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
297 )
298 .unwrap(),
299 U256::from_str(
300 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
301 )
302 .unwrap(),
303 true,
304 ),
305 ),
306 TransactionSigned::new_unhashed(
307 Transaction::Eip1559(TxEip1559 {
308 chain_id: 4,
309 nonce: 26u64,
310 max_priority_fee_per_gas: 1500000000,
311 max_fee_per_gas: 1500000013,
312 gas_limit: MIN_TRANSACTION_GAS,
313 to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
314 value: U256::from(3000000000000000000u64),
315 input: Default::default(),
316 access_list: Default::default(),
317 }),
318 Signature::new(
319 U256::from_str(
320 "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
321 )
322 .unwrap(),
323 U256::from_str(
324 "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
325 )
326 .unwrap(),
327 true,
328 ),
329 ),
330 TransactionSigned::new_unhashed(
331 Transaction::Legacy(TxLegacy {
332 chain_id: Some(4),
333 nonce: 3u64,
334 gas_price: 2000000000,
335 gas_limit: 10000000,
336 to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
337 value: U256::from(1000000000000000u64),
338 input: Default::default(),
339 }),
340 Signature::new(
341 U256::from_str(
342 "0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071",
343 )
344 .unwrap(),
345 U256::from_str(
346 "0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88",
347 )
348 .unwrap(),
349 false,
350 ),
351 ),
352 TransactionSigned::new_unhashed(
353 Transaction::Legacy(TxLegacy {
354 chain_id: Some(4),
355 nonce: 1u64,
356 gas_price: 1000000000,
357 gas_limit: 100000,
358 to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
359 value: U256::from(693361000000000u64),
360 input: Default::default(),
361 }),
362 Signature::new(
363 U256::from_str(
364 "0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a",
365 )
366 .unwrap(),
367 U256::from_str(
368 "0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da",
369 )
370 .unwrap(),
371 false,
372 ),
373 ),
374 TransactionSigned::new_unhashed(
375 Transaction::Legacy(TxLegacy {
376 chain_id: Some(4),
377 nonce: 2u64,
378 gas_price: 1000000000,
379 gas_limit: 100000,
380 to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
381 value: U256::from(1000000000000000u64),
382 input: Default::default(),
383 }),
384 Signature::new(
385 U256::from_str(
386 "0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae",
387 )
388 .unwrap(),
389 U256::from_str(
390 "0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18",
391 )
392 .unwrap(),
393 false,
394 ),
395 ),
396 ];
397 let message: Vec<PooledTransaction> = txs
398 .into_iter()
399 .map(|tx| {
400 PooledTransaction::try_from(tx)
401 .expect("Failed to convert TransactionSigned to PooledTransaction")
402 })
403 .collect();
404 let expected_transactions =
405 RequestPair { request_id: 0, message: PooledTransactions(message) };
406
407 for (decoded, expected) in
409 decoded_transactions.message.0.iter().zip(expected_transactions.message.0.iter())
410 {
411 assert_eq!(decoded, expected);
412 }
413
414 assert_eq!(decoded_transactions, expected_transactions);
415 }
416
417 #[test]
418 fn encode_pooled_transactions_network() {
419 let expected = hex!(
420 "f9022980f90225f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631daf86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18"
421 );
422 let txs = vec![
423 TransactionSigned::new_unhashed(
424 Transaction::Legacy(TxLegacy {
425 chain_id: Some(4),
426 nonce: 15u64,
427 gas_price: 2200000000,
428 gas_limit: 34811,
429 to: TxKind::Call(hex!("cf7f9e66af820a19257a2108375b180b0ec49167").into()),
430 value: U256::from(1234u64),
431 input: Default::default(),
432 }),
433 Signature::new(
434 U256::from_str(
435 "0x35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981",
436 )
437 .unwrap(),
438 U256::from_str(
439 "0x612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860",
440 )
441 .unwrap(),
442 true,
443 ),
444 ),
445 TransactionSigned::new_unhashed(
446 Transaction::Eip1559(TxEip1559 {
447 chain_id: 4,
448 nonce: 26u64,
449 max_priority_fee_per_gas: 1500000000,
450 max_fee_per_gas: 1500000013,
451 gas_limit: MIN_TRANSACTION_GAS,
452 to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
453 value: U256::from(3000000000000000000u64),
454 input: Default::default(),
455 access_list: Default::default(),
456 }),
457 Signature::new(
458 U256::from_str(
459 "0x59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd",
460 )
461 .unwrap(),
462 U256::from_str(
463 "0x016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469",
464 )
465 .unwrap(),
466 true,
467 ),
468 ),
469 TransactionSigned::new_unhashed(
470 Transaction::Legacy(TxLegacy {
471 chain_id: Some(4),
472 nonce: 3u64,
473 gas_price: 2000000000,
474 gas_limit: 10000000,
475 to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
476 value: U256::from(1000000000000000u64),
477 input: Default::default(),
478 }),
479 Signature::new(
480 U256::from_str(
481 "0xce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071",
482 )
483 .unwrap(),
484 U256::from_str(
485 "0x3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88",
486 )
487 .unwrap(),
488 false,
489 ),
490 ),
491 TransactionSigned::new_unhashed(
492 Transaction::Legacy(TxLegacy {
493 chain_id: Some(4),
494 nonce: 1u64,
495 gas_price: 1000000000,
496 gas_limit: 100000,
497 to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
498 value: U256::from(693361000000000u64),
499 input: Default::default(),
500 }),
501 Signature::new(
502 U256::from_str(
503 "0xe24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a",
504 )
505 .unwrap(),
506 U256::from_str(
507 "0x5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da",
508 )
509 .unwrap(),
510 false,
511 ),
512 ),
513 TransactionSigned::new_unhashed(
514 Transaction::Legacy(TxLegacy {
515 chain_id: Some(4),
516 nonce: 2u64,
517 gas_price: 1000000000,
518 gas_limit: 100000,
519 to: TxKind::Call(hex!("d3e8763675e4c425df46cc3b5c0f6cbdac396046").into()),
520 value: U256::from(1000000000000000u64),
521 input: Default::default(),
522 }),
523 Signature::new(
524 U256::from_str(
525 "0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae",
526 )
527 .unwrap(),
528 U256::from_str(
529 "0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18",
530 )
531 .unwrap(),
532 false,
533 ),
534 ),
535 ];
536 let message: Vec<PooledTransaction> = txs
537 .into_iter()
538 .map(|tx| {
539 PooledTransaction::try_from(tx)
540 .expect("Failed to convert TransactionSigned to PooledTransaction")
541 })
542 .collect();
543 let transactions = RequestPair { request_id: 0, message: PooledTransactions(message) };
544
545 let mut encoded = vec![];
546 transactions.encode(&mut encoded);
547 assert_eq!(encoded.len(), transactions.length());
548 let encoded_str = hex::encode(encoded);
549 let expected_str = hex::encode(expected);
550 assert_eq!(encoded_str.len(), expected_str.len());
551 assert_eq!(encoded_str, expected_str);
552 }
553}