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