1use alloc::vec::Vec;
4use alloy_consensus::{ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt};
5use alloy_primitives::B256;
6use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
7use reth_codecs_derive::add_arbitrary_tests;
8use reth_ethereum_primitives::Receipt;
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 GetReceipts(
16 pub Vec<B256>,
18);
19
20#[derive(Clone, Debug, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
30pub struct GetReceipts70 {
31 pub first_block_receipt_index: u64,
33 pub block_hashes: Vec<B256>,
35}
36
37impl alloy_rlp::Encodable for GetReceipts70 {
38 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
39 self.first_block_receipt_index.encode(out);
40 self.block_hashes.encode(out);
41 }
42
43 fn length(&self) -> usize {
44 self.first_block_receipt_index.length() + self.block_hashes.length()
45 }
46}
47
48impl alloy_rlp::Decodable for GetReceipts70 {
49 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
50 let first_block_receipt_index = u64::decode(buf)?;
51 let block_hashes = Vec::<B256>::decode(buf)?;
52 Ok(Self { first_block_receipt_index, block_hashes })
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq, Default)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
61#[add_arbitrary_tests(rlp)]
62pub struct Receipts<T = Receipt>(
63 pub Vec<Vec<ReceiptWithBloom<T>>>,
65);
66
67impl<T: RlpEncodableReceipt> alloy_rlp::Encodable for Receipts<T> {
68 #[inline]
69 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
70 self.0.encode(out)
71 }
72 #[inline]
73 fn length(&self) -> usize {
74 self.0.length()
75 }
76}
77
78impl<T: RlpDecodableReceipt> alloy_rlp::Decodable for Receipts<T> {
79 #[inline]
80 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
81 alloy_rlp::Decodable::decode(buf).map(Self)
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
91#[add_arbitrary_tests(rlp)]
92pub struct Receipts69<T = Receipt>(pub Vec<Vec<T>>);
93
94impl<T: TxReceipt> Receipts69<T> {
95 pub fn into_with_bloom(self) -> Receipts<T> {
105 Receipts(
106 self.0
107 .into_iter()
108 .map(|receipts| receipts.into_iter().map(|r| r.into_with_bloom()).collect())
109 .collect(),
110 )
111 }
112}
113
114impl<T: TxReceipt> From<Receipts69<T>> for Receipts<T> {
115 fn from(receipts: Receipts69<T>) -> Self {
116 receipts.into_with_bloom()
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
127pub struct Receipts70<T = Receipt> {
128 pub last_block_incomplete: bool,
130 pub receipts: Vec<Vec<T>>,
132}
133
134impl<T> alloy_rlp::Encodable for Receipts70<T>
135where
136 T: alloy_rlp::Encodable,
137{
138 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
139 self.last_block_incomplete.encode(out);
140 self.receipts.encode(out);
141 }
142
143 fn length(&self) -> usize {
144 self.last_block_incomplete.length() + self.receipts.length()
145 }
146}
147
148impl<T> alloy_rlp::Decodable for Receipts70<T>
149where
150 T: alloy_rlp::Decodable,
151{
152 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
153 let last_block_incomplete = bool::decode(buf)?;
154 let receipts = Vec::<Vec<T>>::decode(buf)?;
155 Ok(Self { last_block_incomplete, receipts })
156 }
157}
158
159impl<T: TxReceipt> Receipts70<T> {
160 pub fn into_with_bloom(self) -> Receipts<T> {
170 Receipts69(self.receipts).into_with_bloom()
173 }
174}
175
176impl<T: TxReceipt> From<Receipts70<T>> for Receipts<T> {
177 fn from(receipts: Receipts70<T>) -> Self {
178 receipts.into_with_bloom()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::{message::RequestPair, GetReceipts, Receipts};
186 use alloy_consensus::TxType;
187 use alloy_primitives::{hex, Log};
188 use alloy_rlp::{Decodable, Encodable};
189
190 #[test]
191 fn roundtrip_eip1559() {
192 let receipts = Receipts(vec![vec![ReceiptWithBloom {
193 receipt: Receipt { tx_type: TxType::Eip1559, ..Default::default() },
194 logs_bloom: Default::default(),
195 }]]);
196
197 let mut out = vec![];
198 receipts.encode(&mut out);
199
200 let mut out = out.as_slice();
201 let decoded = Receipts::decode(&mut out).unwrap();
202
203 assert_eq!(receipts, decoded);
204 }
205
206 #[test]
207 fn encode_get_receipts() {
209 let expected = hex!(
210 "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
211 );
212 let mut data = vec![];
213 let request = RequestPair {
214 request_id: 1111,
215 message: GetReceipts(vec![
216 hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
217 hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
218 ]),
219 };
220 request.encode(&mut data);
221 assert_eq!(data, expected);
222 }
223
224 #[test]
225 fn decode_get_receipts() {
227 let data = hex!(
228 "f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"
229 );
230 let request = RequestPair::<GetReceipts>::decode(&mut &data[..]).unwrap();
231 assert_eq!(
232 request,
233 RequestPair {
234 request_id: 1111,
235 message: GetReceipts(vec![
236 hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
237 hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
238 ]),
239 }
240 );
241 }
242
243 #[test]
245 fn encode_receipts() {
246 let expected = hex!(
247 "f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
248 );
249 let mut data = vec![];
250 let request = RequestPair {
251 request_id: 1111,
252 message: Receipts(vec![vec![
253 ReceiptWithBloom {
254 receipt: Receipt {
255 tx_type: TxType::Legacy,
256 cumulative_gas_used: 0x1u64,
257 logs: vec![
258 Log::new_unchecked(
259 hex!("0000000000000000000000000000000000000011").into(),
260 vec![
261 hex!("000000000000000000000000000000000000000000000000000000000000dead").into(),
262 hex!("000000000000000000000000000000000000000000000000000000000000beef").into(),
263 ],
264 hex!("0100ff")[..].into(),
265 ),
266 ],
267 success: false,
268 },
269 logs_bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
270 },
271 ]]),
272 };
273 request.encode(&mut data);
274 assert_eq!(data, expected);
275 }
276
277 #[test]
279 fn decode_receipts() {
280 let data = hex!(
281 "f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
282 );
283 let request = RequestPair::<Receipts>::decode(&mut &data[..]).unwrap();
284 assert_eq!(
285 request,
286 RequestPair {
287 request_id: 1111,
288 message: Receipts(vec![
289 vec![
290 ReceiptWithBloom {
291 receipt: Receipt {
292 tx_type: TxType::Legacy,
293 cumulative_gas_used: 0x1u64,
294 logs: vec![
295 Log::new_unchecked(
296 hex!("0000000000000000000000000000000000000011").into(),
297 vec![
298 hex!("000000000000000000000000000000000000000000000000000000000000dead").into(),
299 hex!("000000000000000000000000000000000000000000000000000000000000beef").into(),
300 ],
301 hex!("0100ff")[..].into(),
302 ),
303 ],
304 success: false,
305 },
306 logs_bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
307 },
308 ],
309 ]),
310 }
311 );
312 }
313
314 #[test]
315 fn decode_receipts_69() {
316 let data = hex!("0xf9026605f90262f9025fc60201826590c0c7800183013cd9c0c702018301a2a5c0c7010183027a36c0c702018302e03ec0c7010183034646c0c702018303ac30c0c78001830483b8c0c702018304e9a2c0c780018305c17fc0c7020183062769c0c7800183068d71c0c702018306f35bc0c702018307cb77c0c701018308a382c0c7020183097ab6c0c78080830b0156c0c70101830b6740c0c70201830bcd48c0c70101830c32f6c0c70101830c98e0c0c70201830cfecac0c70201830d64b4c0c70280830dca9ec0c70101830e30a6c0c70201830f080dc0c70201830f6e15c0c78080830fd41dc0c702018310abbac0c701018310fdc2c0c7020183116370c0c780018311c95ac0c7010183122f44c0c701808312952ec0c7020183136c7dc0c70201831443c0c0c702018314a9c8c0c7020183150f94c0c7018083169634c0c7020183176d68c0c702808317d370c0c70201831838c4c0c701808319bf64c0c70201831a256cc0c78080831bac0cc0c70201831c11d8c0c70201831c77c2c0c78080831cdd34c0c70201831db57bc0c70101831e8d07c0c70101831ef2d3c0c70201831fcb37c0c70180832030e5c0c70201832096cfc0c701018320fcb9c0c70201832162c1c0c702018321c8abc0c7020183229ffac0c70201832305c6c0c7028083236bcec0c702808323d1d6c0c702018324a91cc0c7020183250f06c0c70201832574d2c0c7020183264c15c0c70201832723b6c0c70201832789a0c0c702018327ef8ac0c7020183285574c0c702018328bb40c0c702018329212ac0c7028083298714c0c70201832a5e4ec0c70201832ac438c0c70201832b9b72c0c70201832c017ac0");
317
318 let request = RequestPair::<Receipts69>::decode(&mut &data[..]).unwrap();
319 assert_eq!(
320 request.message.0[0][0],
321 Receipt {
322 tx_type: TxType::Eip1559,
323 success: true,
324 cumulative_gas_used: 26000,
325 logs: vec![],
326 }
327 );
328
329 let encoded = alloy_rlp::encode(&request);
330 assert_eq!(encoded, data);
331 }
332
333 #[test]
334 fn encode_get_receipts70_inline_shape() {
335 let req = RequestPair {
336 request_id: 1111,
337 message: GetReceipts70 {
338 first_block_receipt_index: 0,
339 block_hashes: vec![
340 hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
341 hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
342 ],
343 },
344 };
345
346 let mut out = vec![];
347 req.encode(&mut out);
348
349 let mut buf = out.as_slice();
350 let header = alloy_rlp::Header::decode(&mut buf).unwrap();
351 let payload_start = buf.len();
352 let request_id = u64::decode(&mut buf).unwrap();
353 let first_block_receipt_index = u64::decode(&mut buf).unwrap();
354 let block_hashes = Vec::<B256>::decode(&mut buf).unwrap();
355
356 assert!(buf.is_empty(), "buffer not fully consumed");
357 assert_eq!(request_id, 1111);
358 assert_eq!(first_block_receipt_index, 0);
359 assert_eq!(block_hashes.len(), 2);
360 assert_eq!(payload_start - buf.len(), header.payload_length);
362
363 let mut buf = out.as_slice();
364 let decoded = RequestPair::<GetReceipts70>::decode(&mut buf).unwrap();
365 assert!(buf.is_empty(), "buffer not fully consumed on decode");
366 assert_eq!(decoded, req);
367 }
368
369 #[test]
370 fn encode_receipts70_inline_shape() {
371 let payload: Receipts70<Receipt> =
372 Receipts70 { last_block_incomplete: true, receipts: vec![vec![Receipt::default()]] };
373
374 let resp = RequestPair { request_id: 7, message: payload };
375
376 let mut out = vec![];
377 resp.encode(&mut out);
378
379 let mut buf = out.as_slice();
380 let header = alloy_rlp::Header::decode(&mut buf).unwrap();
381 let payload_start = buf.len();
382 let request_id = u64::decode(&mut buf).unwrap();
383 let last_block_incomplete = bool::decode(&mut buf).unwrap();
384 let receipts = Vec::<Vec<Receipt>>::decode(&mut buf).unwrap();
385
386 assert!(buf.is_empty(), "buffer not fully consumed");
387 assert_eq!(payload_start - buf.len(), header.payload_length);
388 assert_eq!(request_id, 7);
389 assert!(last_block_incomplete);
390 assert_eq!(receipts.len(), 1);
391 assert_eq!(receipts[0].len(), 1);
392
393 let mut buf = out.as_slice();
394 let decoded = RequestPair::<Receipts70>::decode(&mut buf).unwrap();
395 assert!(buf.is_empty(), "buffer not fully consumed on decode");
396 assert_eq!(decoded, resp);
397 }
398}