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