1use crate::BlockAccessLists;
9use alloc::vec::Vec;
10use alloy_primitives::{Bytes, B256};
11use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable};
12use reth_codecs_derive::add_arbitrary_tests;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[repr(u8)]
18pub enum SnapVersion {
19 #[default]
21 V2 = 2,
22}
23
24impl SnapVersion {
25 pub const fn message_count(self) -> u8 {
28 match self {
29 Self::V2 => 10,
30 }
31 }
32
33 pub const fn supports_message_id(self, id: u8) -> bool {
38 match self {
39 Self::V2 => {
41 id <= SnapMessageId::ByteCodes as u8 ||
42 id == SnapMessageId::GetBlockAccessLists as u8 ||
43 id == SnapMessageId::BlockAccessLists as u8
44 }
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum SnapMessageId {
52 GetAccountRange = 0x00,
54 AccountRange = 0x01,
57 GetStorageRanges = 0x02,
59 StorageRanges = 0x03,
61 GetByteCodes = 0x04,
63 ByteCodes = 0x05,
65 GetBlockAccessLists = 0x08,
67 BlockAccessLists = 0x09,
69}
70
71impl SnapMessageId {
72 pub const fn response(self) -> Option<Self> {
75 match self {
76 Self::GetAccountRange => Some(Self::AccountRange),
77 Self::GetStorageRanges => Some(Self::StorageRanges),
78 Self::GetByteCodes => Some(Self::ByteCodes),
79 Self::GetBlockAccessLists => Some(Self::BlockAccessLists),
80 Self::AccountRange | Self::StorageRanges | Self::ByteCodes | Self::BlockAccessLists => {
81 None
82 }
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
90#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
91#[add_arbitrary_tests(rlp)]
92pub struct GetAccountRangeMessage {
93 pub request_id: u64,
95 pub root_hash: B256,
97 pub starting_hash: B256,
99 pub limit_hash: B256,
101 pub response_bytes: u64,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
107#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
108#[add_arbitrary_tests(rlp)]
109pub struct AccountData {
110 pub hash: B256,
112 pub body: Bytes,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
119#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
120#[add_arbitrary_tests(rlp)]
121pub struct AccountRangeMessage {
122 pub request_id: u64,
124 pub accounts: Vec<AccountData>,
126 pub proof: Vec<Bytes>,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
133#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
134#[add_arbitrary_tests(rlp)]
135pub struct GetStorageRangesMessage {
136 pub request_id: u64,
138 pub root_hash: B256,
140 pub account_hashes: Vec<B256>,
142 pub starting_hash: B256,
144 pub limit_hash: B256,
146 pub response_bytes: u64,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
152#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
153#[add_arbitrary_tests(rlp)]
154pub struct StorageData {
155 pub hash: B256,
157 pub data: Bytes,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
166#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
167#[add_arbitrary_tests(rlp)]
168pub struct StorageRangesMessage {
169 pub request_id: u64,
171 pub slots: Vec<Vec<StorageData>>,
173 pub proof: Vec<Bytes>,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
180#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
181#[add_arbitrary_tests(rlp)]
182pub struct GetByteCodesMessage {
183 pub request_id: u64,
185 pub hashes: Vec<B256>,
187 pub response_bytes: u64,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
194#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
195#[add_arbitrary_tests(rlp)]
196pub struct ByteCodesMessage {
197 pub request_id: u64,
199 pub codes: Vec<Bytes>,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
205#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
206#[add_arbitrary_tests(rlp)]
207pub struct GetBlockAccessListsMessage {
208 pub request_id: u64,
210 pub block_hashes: Vec<B256>,
212 pub response_bytes: u64,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
218#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
219#[add_arbitrary_tests(rlp)]
220pub struct BlockAccessListsMessage {
221 pub request_id: u64,
223 pub block_access_lists: BlockAccessLists,
225}
226
227#[derive(Debug, Clone, PartialEq, Eq)]
229pub enum SnapProtocolMessage {
230 GetAccountRange(GetAccountRangeMessage),
232 AccountRange(AccountRangeMessage),
234 GetStorageRanges(GetStorageRangesMessage),
236 StorageRanges(StorageRangesMessage),
238 GetByteCodes(GetByteCodesMessage),
240 ByteCodes(ByteCodesMessage),
242 GetBlockAccessLists(GetBlockAccessListsMessage),
244 BlockAccessLists(BlockAccessListsMessage),
246}
247
248#[derive(thiserror::Error, Debug)]
250pub enum SnapProtocolError {
251 #[error("empty snap message")]
253 Empty,
254 #[error("message id {0:#x} is invalid for snap/{1:?}")]
257 UnsupportedMessageId(u8, SnapVersion),
258 #[error("RLP error: {0}")]
260 Rlp(#[from] alloy_rlp::Error),
261}
262
263impl SnapProtocolMessage {
264 pub const fn message_id(&self) -> SnapMessageId {
268 match self {
269 Self::GetAccountRange(_) => SnapMessageId::GetAccountRange,
270 Self::AccountRange(_) => SnapMessageId::AccountRange,
271 Self::GetStorageRanges(_) => SnapMessageId::GetStorageRanges,
272 Self::StorageRanges(_) => SnapMessageId::StorageRanges,
273 Self::GetByteCodes(_) => SnapMessageId::GetByteCodes,
274 Self::ByteCodes(_) => SnapMessageId::ByteCodes,
275 Self::GetBlockAccessLists(_) => SnapMessageId::GetBlockAccessLists,
276 Self::BlockAccessLists(_) => SnapMessageId::BlockAccessLists,
277 }
278 }
279
280 pub const fn request_id(&self) -> u64 {
282 match self {
283 Self::GetAccountRange(m) => m.request_id,
284 Self::AccountRange(m) => m.request_id,
285 Self::GetStorageRanges(m) => m.request_id,
286 Self::StorageRanges(m) => m.request_id,
287 Self::GetByteCodes(m) => m.request_id,
288 Self::ByteCodes(m) => m.request_id,
289 Self::GetBlockAccessLists(m) => m.request_id,
290 Self::BlockAccessLists(m) => m.request_id,
291 }
292 }
293
294 pub const fn is_response(&self) -> bool {
296 matches!(
297 self,
298 Self::AccountRange(_) |
299 Self::StorageRanges(_) |
300 Self::ByteCodes(_) |
301 Self::BlockAccessLists(_)
302 )
303 }
304
305 pub const fn set_request_id(&mut self, request_id: u64) {
308 match self {
309 Self::GetAccountRange(m) => m.request_id = request_id,
310 Self::AccountRange(m) => m.request_id = request_id,
311 Self::GetStorageRanges(m) => m.request_id = request_id,
312 Self::StorageRanges(m) => m.request_id = request_id,
313 Self::GetByteCodes(m) => m.request_id = request_id,
314 Self::ByteCodes(m) => m.request_id = request_id,
315 Self::GetBlockAccessLists(m) => m.request_id = request_id,
316 Self::BlockAccessLists(m) => m.request_id = request_id,
317 }
318 }
319
320 pub fn encode(&self) -> Bytes {
322 let mut buf = Vec::new();
323 buf.push(self.message_id() as u8);
325
326 match self {
328 Self::GetAccountRange(msg) => msg.encode(&mut buf),
329 Self::AccountRange(msg) => msg.encode(&mut buf),
330 Self::GetStorageRanges(msg) => msg.encode(&mut buf),
331 Self::StorageRanges(msg) => msg.encode(&mut buf),
332 Self::GetByteCodes(msg) => msg.encode(&mut buf),
333 Self::ByteCodes(msg) => msg.encode(&mut buf),
334 Self::GetBlockAccessLists(msg) => msg.encode(&mut buf),
335 Self::BlockAccessLists(msg) => msg.encode(&mut buf),
336 }
337
338 Bytes::from(buf)
339 }
340
341 pub fn decode(message_id: u8, buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
343 macro_rules! decode_snap_message_variant {
345 ($message_id:expr, $buf:expr, $id:expr, $variant:ident, $msg_type:ty) => {
346 if $message_id == $id as u8 {
347 return Ok(Self::$variant(<$msg_type>::decode($buf)?));
348 }
349 };
350 }
351
352 decode_snap_message_variant!(
354 message_id,
355 buf,
356 SnapMessageId::GetAccountRange,
357 GetAccountRange,
358 GetAccountRangeMessage
359 );
360 decode_snap_message_variant!(
361 message_id,
362 buf,
363 SnapMessageId::AccountRange,
364 AccountRange,
365 AccountRangeMessage
366 );
367 decode_snap_message_variant!(
368 message_id,
369 buf,
370 SnapMessageId::GetStorageRanges,
371 GetStorageRanges,
372 GetStorageRangesMessage
373 );
374 decode_snap_message_variant!(
375 message_id,
376 buf,
377 SnapMessageId::StorageRanges,
378 StorageRanges,
379 StorageRangesMessage
380 );
381 decode_snap_message_variant!(
382 message_id,
383 buf,
384 SnapMessageId::GetByteCodes,
385 GetByteCodes,
386 GetByteCodesMessage
387 );
388 decode_snap_message_variant!(
389 message_id,
390 buf,
391 SnapMessageId::ByteCodes,
392 ByteCodes,
393 ByteCodesMessage
394 );
395 decode_snap_message_variant!(
396 message_id,
397 buf,
398 SnapMessageId::GetBlockAccessLists,
399 GetBlockAccessLists,
400 GetBlockAccessListsMessage
401 );
402 decode_snap_message_variant!(
403 message_id,
404 buf,
405 SnapMessageId::BlockAccessLists,
406 BlockAccessLists,
407 BlockAccessListsMessage
408 );
409
410 Err(alloy_rlp::Error::Custom("Unknown message ID"))
411 }
412
413 pub fn decode_versioned(version: SnapVersion, bytes: &[u8]) -> Result<Self, SnapProtocolError> {
418 let (&id, mut body) = bytes.split_first().ok_or(SnapProtocolError::Empty)?;
419 if !version.supports_message_id(id) {
420 return Err(SnapProtocolError::UnsupportedMessageId(id, version));
421 }
422 let msg = Self::decode(id, &mut body)?;
423 if !body.is_empty() {
424 return Err(SnapProtocolError::Rlp(alloy_rlp::Error::UnexpectedLength));
425 }
426 Ok(msg)
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use test_case::test_case;
434
435 fn b256_from_u64(value: u64) -> B256 {
437 B256::left_padding_from(&value.to_be_bytes())
438 }
439
440 fn test_roundtrip(original: SnapProtocolMessage) {
442 let encoded = original.encode();
443
444 assert_eq!(encoded[0], original.message_id() as u8);
446
447 let mut buf = &encoded[1..];
448 let decoded = SnapProtocolMessage::decode(encoded[0], &mut buf).unwrap();
449
450 assert_eq!(decoded, original);
452 }
453
454 #[test]
455 fn test_all_message_roundtrips() {
456 assert_eq!(SnapVersion::V2.message_count(), 10);
457
458 test_roundtrip(SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
459 request_id: 42,
460 root_hash: b256_from_u64(123),
461 starting_hash: b256_from_u64(456),
462 limit_hash: b256_from_u64(789),
463 response_bytes: 1024,
464 }));
465
466 test_roundtrip(SnapProtocolMessage::AccountRange(AccountRangeMessage {
467 request_id: 42,
468 accounts: vec![AccountData {
469 hash: b256_from_u64(123),
470 body: Bytes::from(vec![1, 2, 3]),
471 }],
472 proof: vec![Bytes::from(vec![4, 5, 6])],
473 }));
474
475 test_roundtrip(SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
476 request_id: 42,
477 root_hash: b256_from_u64(123),
478 account_hashes: vec![b256_from_u64(456)],
479 starting_hash: b256_from_u64(789),
480 limit_hash: b256_from_u64(101112),
481 response_bytes: 2048,
482 }));
483
484 test_roundtrip(SnapProtocolMessage::StorageRanges(StorageRangesMessage {
485 request_id: 42,
486 slots: vec![vec![StorageData {
487 hash: b256_from_u64(123),
488 data: Bytes::from(vec![1, 2, 3]),
489 }]],
490 proof: vec![Bytes::from(vec![4, 5, 6])],
491 }));
492
493 test_roundtrip(SnapProtocolMessage::GetByteCodes(GetByteCodesMessage {
494 request_id: 42,
495 hashes: vec![b256_from_u64(123)],
496 response_bytes: 1024,
497 }));
498
499 test_roundtrip(SnapProtocolMessage::ByteCodes(ByteCodesMessage {
500 request_id: 42,
501 codes: vec![Bytes::from(vec![1, 2, 3])],
502 }));
503
504 test_roundtrip(SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
505 request_id: 42,
506 block_hashes: vec![b256_from_u64(123), b256_from_u64(456)],
507 response_bytes: 4096,
508 }));
509
510 test_roundtrip(SnapProtocolMessage::BlockAccessLists(BlockAccessListsMessage {
511 request_id: 42,
512 block_access_lists: BlockAccessLists(vec![
513 Some(Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE])),
514 Some(Bytes::from_static(&[0xc1, alloy_rlp::EMPTY_LIST_CODE])),
515 ]),
516 }));
517 }
518
519 #[test]
520 fn test_unknown_message_id() {
521 let data = Bytes::from(vec![1, 2, 3, 4]);
523 let mut buf = data.as_ref();
524
525 let result = SnapProtocolMessage::decode(255, &mut buf);
527
528 assert!(result.is_err());
529 if let Err(e) = result {
530 assert_eq!(e.to_string(), "Unknown message ID");
531 }
532 }
533
534 #[test]
535 fn test_snap_v2_message_validity() {
536 let v2 = SnapVersion::V2;
537 for id in 0x00..=0x05 {
539 assert!(v2.supports_message_id(id), "snap/2 should accept {id:#x}");
540 }
541 assert!(!v2.supports_message_id(0x06));
543 assert!(!v2.supports_message_id(0x07));
544 assert!(v2.supports_message_id(SnapMessageId::GetBlockAccessLists as u8));
546 assert!(v2.supports_message_id(SnapMessageId::BlockAccessLists as u8));
547 assert!(!v2.supports_message_id(0x0a));
548 assert!(!v2.supports_message_id(0xff));
549 }
550
551 #[test_case(
552 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
553 request_id: 1, root_hash: B256::ZERO, starting_hash: B256::ZERO,
554 limit_hash: B256::ZERO, response_bytes: 0,
555 }), 1, false ; "get_account_range is a request"
556 )]
557 #[test_case(
558 SnapProtocolMessage::AccountRange(AccountRangeMessage {
559 request_id: 2, accounts: vec![], proof: vec![],
560 }), 2, true ; "account_range is a response"
561 )]
562 #[test_case(
563 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
564 request_id: 3, root_hash: B256::ZERO, account_hashes: vec![],
565 starting_hash: B256::ZERO, limit_hash: B256::ZERO, response_bytes: 0,
566 }), 3, false ; "get_storage_ranges is a request"
567 )]
568 #[test_case(
569 SnapProtocolMessage::StorageRanges(StorageRangesMessage {
570 request_id: 4, slots: vec![], proof: vec![],
571 }), 4, true ; "storage_ranges is a response"
572 )]
573 #[test_case(
574 SnapProtocolMessage::GetByteCodes(GetByteCodesMessage {
575 request_id: 5, hashes: vec![], response_bytes: 0,
576 }), 5, false ; "get_byte_codes is a request"
577 )]
578 #[test_case(
579 SnapProtocolMessage::ByteCodes(ByteCodesMessage { request_id: 6, codes: vec![] }),
580 6, true ; "byte_codes is a response"
581 )]
582 #[test_case(
583 SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
584 request_id: 7, block_hashes: vec![], response_bytes: 0,
585 }), 7, false ; "get_block_access_lists is a request"
586 )]
587 #[test_case(
588 SnapProtocolMessage::BlockAccessLists(BlockAccessListsMessage {
589 request_id: 8, block_access_lists: BlockAccessLists(vec![]),
590 }), 8, true ; "block_access_lists is a response"
591 )]
592 fn request_id_and_is_response(msg: SnapProtocolMessage, expected_id: u64, is_response: bool) {
593 assert_eq!(msg.request_id(), expected_id);
594 assert_eq!(msg.is_response(), is_response);
595 }
596
597 #[test_case(
598 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
599 request_id: 1, root_hash: B256::ZERO, starting_hash: B256::ZERO,
600 limit_hash: B256::ZERO, response_bytes: 0,
601 }) ; "get_account_range"
602 )]
603 #[test_case(
604 SnapProtocolMessage::AccountRange(AccountRangeMessage {
605 request_id: 1, accounts: vec![], proof: vec![],
606 }) ; "account_range"
607 )]
608 #[test_case(
609 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
610 request_id: 1, root_hash: B256::ZERO, account_hashes: vec![],
611 starting_hash: B256::ZERO, limit_hash: B256::ZERO, response_bytes: 0,
612 }) ; "get_storage_ranges"
613 )]
614 #[test_case(
615 SnapProtocolMessage::StorageRanges(StorageRangesMessage {
616 request_id: 1, slots: vec![], proof: vec![],
617 }) ; "storage_ranges"
618 )]
619 #[test_case(
620 SnapProtocolMessage::GetByteCodes(GetByteCodesMessage {
621 request_id: 1, hashes: vec![], response_bytes: 0,
622 }) ; "get_byte_codes"
623 )]
624 #[test_case(
625 SnapProtocolMessage::ByteCodes(ByteCodesMessage { request_id: 1, codes: vec![] }) ;
626 "byte_codes"
627 )]
628 #[test_case(
629 SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
630 request_id: 1, block_hashes: vec![], response_bytes: 0,
631 }) ; "get_block_access_lists"
632 )]
633 #[test_case(
634 SnapProtocolMessage::BlockAccessLists(BlockAccessListsMessage {
635 request_id: 1, block_access_lists: BlockAccessLists(vec![]),
636 }) ; "block_access_lists"
637 )]
638 fn per_variant_request_id_and_round_trip(mut msg: SnapProtocolMessage) {
639 msg.set_request_id(42);
641 assert_eq!(msg.request_id(), 42);
642
643 let decoded =
645 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &msg.encode()).unwrap();
646 assert_eq!(decoded, msg);
647 }
648
649 #[test]
650 fn decode_versioned_rejects_empty() {
651 assert!(matches!(
653 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &[]),
654 Err(SnapProtocolError::Empty)
655 ));
656 }
657
658 #[test]
659 fn decode_versioned_rejects_trie_node_ids_in_v2() {
660 for id in [0x06u8, 0x07] {
663 assert!(matches!(
664 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &[id]),
665 Err(SnapProtocolError::UnsupportedMessageId(got, SnapVersion::V2)) if got == id
666 ));
667 }
668 }
669
670 #[test]
671 fn decode_versioned_reports_malformed_body() {
672 assert!(matches!(
675 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &[0x08, 0xff]),
676 Err(SnapProtocolError::Rlp(_))
677 ));
678 }
679
680 #[test]
681 fn decode_versioned_rejects_trailing_bytes() {
682 let original = SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
685 request_id: 7,
686 block_hashes: vec![b256_from_u64(1)],
687 response_bytes: 1024,
688 });
689 let mut framed = original.encode().to_vec();
690 framed.push(0xff);
691 assert!(matches!(
692 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &framed),
693 Err(SnapProtocolError::Rlp(alloy_rlp::Error::UnexpectedLength))
694 ));
695 }
696}