1use crate::BlockAccessLists;
9use alloc::vec::Vec;
10use alloy_primitives::{Bytes, B256, KECCAK256_EMPTY, U256};
11use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable};
12use alloy_trie::{TrieAccount, EMPTY_ROOT_HASH};
13use reth_codecs_derive::add_arbitrary_tests;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[repr(u8)]
19pub enum SnapVersion {
20 #[default]
22 V2 = 2,
23}
24
25impl SnapVersion {
26 pub const fn message_count(self) -> u8 {
29 match self {
30 Self::V2 => 10,
31 }
32 }
33
34 pub const fn supports_message_id(self, id: u8) -> bool {
39 match self {
40 Self::V2 => {
42 id <= SnapMessageId::ByteCodes as u8 ||
43 id == SnapMessageId::GetBlockAccessLists as u8 ||
44 id == SnapMessageId::BlockAccessLists as u8
45 }
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum SnapMessageId {
53 GetAccountRange = 0x00,
55 AccountRange = 0x01,
58 GetStorageRanges = 0x02,
60 StorageRanges = 0x03,
62 GetByteCodes = 0x04,
64 ByteCodes = 0x05,
66 GetBlockAccessLists = 0x08,
68 BlockAccessLists = 0x09,
70}
71
72impl SnapMessageId {
73 pub const fn response(self) -> Option<Self> {
76 match self {
77 Self::GetAccountRange => Some(Self::AccountRange),
78 Self::GetStorageRanges => Some(Self::StorageRanges),
79 Self::GetByteCodes => Some(Self::ByteCodes),
80 Self::GetBlockAccessLists => Some(Self::BlockAccessLists),
81 Self::AccountRange | Self::StorageRanges | Self::ByteCodes | Self::BlockAccessLists => {
82 None
83 }
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
91#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
92#[add_arbitrary_tests(rlp)]
93pub struct GetAccountRangeMessage {
94 pub request_id: u64,
96 pub root_hash: B256,
98 pub starting_hash: B256,
100 pub limit_hash: B256,
102 pub response_bytes: u64,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
108#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
109#[add_arbitrary_tests(rlp)]
110pub struct AccountData {
111 pub hash: B256,
113 pub body: SlimAccountBody,
115}
116
117impl AccountData {
118 pub fn from_trie_account(hash: B256, account: &TrieAccount) -> Self {
120 Self { hash, body: account.into() }
121 }
122
123 #[allow(clippy::clone_on_copy)]
127 pub fn trie_account(&self) -> TrieAccount {
128 self.body.0.clone()
129 }
130
131 #[allow(clippy::missing_const_for_fn)]
133 pub fn into_trie_entry(self) -> (B256, TrieAccount) {
134 (self.hash, self.body.0)
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
141#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
142#[add_arbitrary_tests(rlp)]
143pub struct AccountRangeMessage {
144 pub request_id: u64,
146 pub accounts: Vec<AccountData>,
148 pub proof: Vec<Bytes>,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
155#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
156#[add_arbitrary_tests(rlp)]
157pub struct GetStorageRangesMessage {
158 pub request_id: u64,
160 pub root_hash: B256,
162 pub account_hashes: Vec<B256>,
164 pub starting_hash: RangeBound,
167 pub limit_hash: RangeBound,
170 pub response_bytes: u64,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
178#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
179pub struct RangeBound(Option<B256>);
180
181impl RangeBound {
182 pub const fn unwrap_or(self, default: B256) -> B256 {
184 match self.0 {
185 Some(hash) => hash,
186 None => default,
187 }
188 }
189}
190
191impl From<B256> for RangeBound {
192 fn from(hash: B256) -> Self {
193 Self(Some(hash))
194 }
195}
196
197impl Encodable for RangeBound {
198 fn encode(&self, out: &mut dyn BufMut) {
199 match self.0 {
200 Some(hash) => hash.encode(out),
201 None => Bytes::new().encode(out),
202 }
203 }
204
205 fn length(&self) -> usize {
206 match self.0 {
207 Some(hash) => hash.length(),
208 None => Bytes::new().length(),
209 }
210 }
211}
212
213impl Decodable for RangeBound {
214 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
215 let bytes = Bytes::decode(buf)?;
216 match bytes.len() {
217 0 => Ok(Self(None)),
218 32 => Ok(Self(Some(B256::from_slice(&bytes)))),
219 _ => Err(alloy_rlp::Error::UnexpectedLength),
220 }
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
226#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
227#[add_arbitrary_tests(rlp)]
228pub struct StorageData {
229 pub hash: B256,
231 pub data: Bytes,
233}
234
235impl StorageData {
236 pub fn from_value(hash: B256, value: U256) -> Self {
238 Self { hash, data: alloy_rlp::encode(value).into() }
239 }
240
241 pub fn value(&self) -> alloy_rlp::Result<U256> {
243 alloy_rlp::decode_exact(&self.data)
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
252#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
253#[add_arbitrary_tests(rlp)]
254pub struct StorageRangesMessage {
255 pub request_id: u64,
257 pub slots: Vec<Vec<StorageData>>,
259 pub proof: Vec<Bytes>,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
266#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
267#[add_arbitrary_tests(rlp)]
268pub struct GetByteCodesMessage {
269 pub request_id: u64,
271 pub hashes: Vec<B256>,
273 pub response_bytes: u64,
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
280#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
281#[add_arbitrary_tests(rlp)]
282pub struct ByteCodesMessage {
283 pub request_id: u64,
285 pub codes: Vec<Bytes>,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
291#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
292#[add_arbitrary_tests(rlp)]
293pub struct GetBlockAccessListsMessage {
294 pub request_id: u64,
296 pub block_hashes: Vec<B256>,
298 pub response_bytes: u64,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
304#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
305#[add_arbitrary_tests(rlp)]
306pub struct BlockAccessListsMessage {
307 pub request_id: u64,
309 pub block_access_lists: BlockAccessLists,
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
315pub enum SnapProtocolMessage {
316 GetAccountRange(GetAccountRangeMessage),
318 AccountRange(AccountRangeMessage),
320 GetStorageRanges(GetStorageRangesMessage),
322 StorageRanges(StorageRangesMessage),
324 GetByteCodes(GetByteCodesMessage),
326 ByteCodes(ByteCodesMessage),
328 GetBlockAccessLists(GetBlockAccessListsMessage),
330 BlockAccessLists(BlockAccessListsMessage),
332}
333
334#[derive(thiserror::Error, Debug)]
337pub enum SnapProtocolError {
338 #[error("empty snap message")]
340 Empty,
341 #[error("message id {0:#x} is invalid for snap/{1:?}")]
344 UnsupportedMessageId(u8, SnapVersion),
345 #[error("RLP error: {0}")]
347 Rlp(#[from] alloy_rlp::Error),
348 #[error("received unsolicited {message_id:?} response with request id {request_id}")]
352 UnsolicitedResponse {
353 message_id: SnapMessageId,
355 request_id: u64,
357 },
358 #[error(
363 "{message_id:?} response with request id {request_id} holds a list longer than the {max_items} items its request allows"
364 )]
365 ResponseExceedsRequest {
366 message_id: SnapMessageId,
368 request_id: u64,
370 max_items: usize,
372 },
373}
374
375impl SnapProtocolMessage {
376 pub const fn message_id(&self) -> SnapMessageId {
380 match self {
381 Self::GetAccountRange(_) => SnapMessageId::GetAccountRange,
382 Self::AccountRange(_) => SnapMessageId::AccountRange,
383 Self::GetStorageRanges(_) => SnapMessageId::GetStorageRanges,
384 Self::StorageRanges(_) => SnapMessageId::StorageRanges,
385 Self::GetByteCodes(_) => SnapMessageId::GetByteCodes,
386 Self::ByteCodes(_) => SnapMessageId::ByteCodes,
387 Self::GetBlockAccessLists(_) => SnapMessageId::GetBlockAccessLists,
388 Self::BlockAccessLists(_) => SnapMessageId::BlockAccessLists,
389 }
390 }
391
392 pub const fn request_id(&self) -> u64 {
394 match self {
395 Self::GetAccountRange(m) => m.request_id,
396 Self::AccountRange(m) => m.request_id,
397 Self::GetStorageRanges(m) => m.request_id,
398 Self::StorageRanges(m) => m.request_id,
399 Self::GetByteCodes(m) => m.request_id,
400 Self::ByteCodes(m) => m.request_id,
401 Self::GetBlockAccessLists(m) => m.request_id,
402 Self::BlockAccessLists(m) => m.request_id,
403 }
404 }
405
406 pub const fn is_response(&self) -> bool {
408 matches!(
409 self,
410 Self::AccountRange(_) |
411 Self::StorageRanges(_) |
412 Self::ByteCodes(_) |
413 Self::BlockAccessLists(_)
414 )
415 }
416
417 pub const fn set_request_id(&mut self, request_id: u64) {
420 match self {
421 Self::GetAccountRange(m) => m.request_id = request_id,
422 Self::AccountRange(m) => m.request_id = request_id,
423 Self::GetStorageRanges(m) => m.request_id = request_id,
424 Self::StorageRanges(m) => m.request_id = request_id,
425 Self::GetByteCodes(m) => m.request_id = request_id,
426 Self::ByteCodes(m) => m.request_id = request_id,
427 Self::GetBlockAccessLists(m) => m.request_id = request_id,
428 Self::BlockAccessLists(m) => m.request_id = request_id,
429 }
430 }
431
432 pub fn encode(&self) -> Bytes {
434 let mut buf = Vec::new();
435 buf.push(self.message_id() as u8);
437
438 match self {
440 Self::GetAccountRange(msg) => msg.encode(&mut buf),
441 Self::AccountRange(msg) => msg.encode(&mut buf),
442 Self::GetStorageRanges(msg) => msg.encode(&mut buf),
443 Self::StorageRanges(msg) => msg.encode(&mut buf),
444 Self::GetByteCodes(msg) => msg.encode(&mut buf),
445 Self::ByteCodes(msg) => msg.encode(&mut buf),
446 Self::GetBlockAccessLists(msg) => msg.encode(&mut buf),
447 Self::BlockAccessLists(msg) => msg.encode(&mut buf),
448 }
449
450 Bytes::from(buf)
451 }
452
453 pub fn decode(message_id: u8, buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
455 macro_rules! decode_snap_message_variant {
457 ($message_id:expr, $buf:expr, $id:expr, $variant:ident, $msg_type:ty) => {
458 if $message_id == $id as u8 {
459 return Ok(Self::$variant(<$msg_type>::decode($buf)?));
460 }
461 };
462 }
463
464 decode_snap_message_variant!(
466 message_id,
467 buf,
468 SnapMessageId::GetAccountRange,
469 GetAccountRange,
470 GetAccountRangeMessage
471 );
472 decode_snap_message_variant!(
473 message_id,
474 buf,
475 SnapMessageId::AccountRange,
476 AccountRange,
477 AccountRangeMessage
478 );
479 decode_snap_message_variant!(
480 message_id,
481 buf,
482 SnapMessageId::GetStorageRanges,
483 GetStorageRanges,
484 GetStorageRangesMessage
485 );
486 decode_snap_message_variant!(
487 message_id,
488 buf,
489 SnapMessageId::StorageRanges,
490 StorageRanges,
491 StorageRangesMessage
492 );
493 decode_snap_message_variant!(
494 message_id,
495 buf,
496 SnapMessageId::GetByteCodes,
497 GetByteCodes,
498 GetByteCodesMessage
499 );
500 decode_snap_message_variant!(
501 message_id,
502 buf,
503 SnapMessageId::ByteCodes,
504 ByteCodes,
505 ByteCodesMessage
506 );
507 decode_snap_message_variant!(
508 message_id,
509 buf,
510 SnapMessageId::GetBlockAccessLists,
511 GetBlockAccessLists,
512 GetBlockAccessListsMessage
513 );
514 decode_snap_message_variant!(
515 message_id,
516 buf,
517 SnapMessageId::BlockAccessLists,
518 BlockAccessLists,
519 BlockAccessListsMessage
520 );
521
522 Err(alloy_rlp::Error::Custom("Unknown message ID"))
523 }
524
525 pub fn decode_versioned(version: SnapVersion, bytes: &[u8]) -> Result<Self, SnapProtocolError> {
530 let (&id, mut body) = bytes.split_first().ok_or(SnapProtocolError::Empty)?;
531 if !version.supports_message_id(id) {
532 return Err(SnapProtocolError::UnsupportedMessageId(id, version));
533 }
534 let msg = Self::decode(id, &mut body)?;
535 if !body.is_empty() {
536 return Err(SnapProtocolError::Rlp(alloy_rlp::Error::UnexpectedLength));
537 }
538 Ok(msg)
539 }
540}
541
542#[derive(Debug, Clone, PartialEq, Eq)]
544pub struct SlimAccountBody(TrieAccount);
545
546impl SlimAccountBody {
547 fn as_rlp(&self) -> SlimAccountBodyRef<'_> {
548 SlimAccountBodyRef {
549 nonce: self.0.nonce,
550 balance: self.0.balance,
551 storage_root: SlimAccountBodyRef::shorten(&self.0.storage_root, EMPTY_ROOT_HASH),
552 code_hash: SlimAccountBodyRef::shorten(&self.0.code_hash, KECCAK256_EMPTY),
553 }
554 }
555
556 fn restore(value: &[u8], empty: B256) -> alloy_rlp::Result<B256> {
558 match value {
559 [] => Ok(empty),
560 _ => B256::try_from(value).map_err(|_| alloy_rlp::Error::UnexpectedLength),
561 }
562 }
563}
564
565impl From<&TrieAccount> for SlimAccountBody {
566 #[allow(clippy::clone_on_copy)]
567 fn from(account: &TrieAccount) -> Self {
568 Self(account.clone())
569 }
570}
571
572impl Encodable for SlimAccountBody {
573 fn encode(&self, out: &mut dyn BufMut) {
574 self.as_rlp().encode(out);
575 }
576
577 fn length(&self) -> usize {
578 self.as_rlp().length()
579 }
580}
581
582impl Decodable for SlimAccountBody {
583 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
584 let mut payload = Header::decode_bytes(buf, true)?;
585 let nonce = u64::decode(&mut payload)?;
586 let balance = U256::decode(&mut payload)?;
587 let storage_root =
588 Self::restore(Header::decode_bytes(&mut payload, false)?, EMPTY_ROOT_HASH)?;
589 let code_hash = Self::restore(Header::decode_bytes(&mut payload, false)?, KECCAK256_EMPTY)?;
590 if !payload.is_empty() {
591 return Err(alloy_rlp::Error::UnexpectedLength)
592 }
593 Ok(Self(TrieAccount::new(nonce, balance, storage_root, code_hash)))
594 }
595}
596
597#[cfg(any(test, feature = "arbitrary"))]
598impl<'a> arbitrary::Arbitrary<'a> for SlimAccountBody {
599 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
600 let storage_root = if u.arbitrary()? { u.arbitrary()? } else { EMPTY_ROOT_HASH };
601 let code_hash = if u.arbitrary()? { u.arbitrary()? } else { KECCAK256_EMPTY };
602 Ok(Self(TrieAccount::new(u.arbitrary()?, u.arbitrary()?, storage_root, code_hash)))
603 }
604}
605
606#[derive(RlpEncodable)]
608struct SlimAccountBodyRef<'a> {
609 nonce: u64,
611 balance: U256,
613 storage_root: &'a [u8],
615 code_hash: &'a [u8],
617}
618
619impl<'a> SlimAccountBodyRef<'a> {
620 fn shorten(value: &'a B256, empty: B256) -> &'a [u8] {
622 if *value == empty {
623 &[]
624 } else {
625 value.as_slice()
626 }
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633 use test_case::test_case;
634
635 fn b256_from_u64(value: u64) -> B256 {
637 B256::left_padding_from(&value.to_be_bytes())
638 }
639
640 fn test_roundtrip(original: SnapProtocolMessage) {
642 let encoded = original.encode();
643
644 assert_eq!(encoded[0], original.message_id() as u8);
646
647 let mut buf = &encoded[1..];
648 let decoded = SnapProtocolMessage::decode(encoded[0], &mut buf).unwrap();
649
650 assert_eq!(decoded, original);
652 }
653
654 #[derive(alloy_rlp::RlpEncodable)]
658 struct GethStorageRequest {
659 request_id: u64,
660 root_hash: B256,
661 account_hashes: Vec<B256>,
662 origin: Bytes,
663 limit: Bytes,
664 response_bytes: u64,
665 }
666
667 #[test]
668 fn test_all_message_roundtrips() {
669 assert_eq!(SnapVersion::V2.message_count(), 10);
670
671 test_roundtrip(SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
672 request_id: 42,
673 root_hash: b256_from_u64(123),
674 starting_hash: b256_from_u64(456),
675 limit_hash: b256_from_u64(789),
676 response_bytes: 1024,
677 }));
678
679 test_roundtrip(SnapProtocolMessage::AccountRange(AccountRangeMessage {
680 request_id: 42,
681 accounts: vec![AccountData::from_trie_account(
682 b256_from_u64(123),
683 &trie_account(EMPTY_ROOT_HASH, KECCAK256_EMPTY),
684 )],
685 proof: vec![Bytes::from(vec![4, 5, 6])],
686 }));
687
688 test_roundtrip(SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
689 request_id: 42,
690 root_hash: b256_from_u64(123),
691 account_hashes: vec![b256_from_u64(456)],
692 starting_hash: b256_from_u64(789).into(),
693 limit_hash: b256_from_u64(101112).into(),
694 response_bytes: 2048,
695 }));
696
697 test_roundtrip(SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
699 request_id: 43,
700 root_hash: b256_from_u64(123),
701 account_hashes: vec![b256_from_u64(456), b256_from_u64(789)],
702 starting_hash: RangeBound::default(),
703 limit_hash: RangeBound::default(),
704 response_bytes: 2048,
705 }));
706
707 test_roundtrip(SnapProtocolMessage::StorageRanges(StorageRangesMessage {
708 request_id: 42,
709 slots: vec![vec![StorageData {
710 hash: b256_from_u64(123),
711 data: Bytes::from(vec![1, 2, 3]),
712 }]],
713 proof: vec![Bytes::from(vec![4, 5, 6])],
714 }));
715
716 test_roundtrip(SnapProtocolMessage::GetByteCodes(GetByteCodesMessage {
717 request_id: 42,
718 hashes: vec![b256_from_u64(123)],
719 response_bytes: 1024,
720 }));
721
722 test_roundtrip(SnapProtocolMessage::ByteCodes(ByteCodesMessage {
723 request_id: 42,
724 codes: vec![Bytes::from(vec![1, 2, 3])],
725 }));
726
727 test_roundtrip(SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
728 request_id: 42,
729 block_hashes: vec![b256_from_u64(123), b256_from_u64(456)],
730 response_bytes: 4096,
731 }));
732
733 test_roundtrip(SnapProtocolMessage::BlockAccessLists(BlockAccessListsMessage {
734 request_id: 42,
735 block_access_lists: BlockAccessLists(vec![
736 Some(Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE])),
737 Some(Bytes::from_static(&[0xc1, alloy_rlp::EMPTY_LIST_CODE])),
738 ]),
739 }));
740 }
741
742 #[test]
743 fn test_unknown_message_id() {
744 let data = Bytes::from(vec![1, 2, 3, 4]);
746 let mut buf = data.as_ref();
747
748 let result = SnapProtocolMessage::decode(255, &mut buf);
750
751 assert!(result.is_err());
752 if let Err(e) = result {
753 assert_eq!(e.to_string(), "Unknown message ID");
754 }
755 }
756
757 #[test]
758 fn test_snap_v2_message_validity() {
759 let v2 = SnapVersion::V2;
760 for id in 0x00..=0x05 {
762 assert!(v2.supports_message_id(id), "snap/2 should accept {id:#x}");
763 }
764 assert!(!v2.supports_message_id(0x06));
766 assert!(!v2.supports_message_id(0x07));
767 assert!(v2.supports_message_id(SnapMessageId::GetBlockAccessLists as u8));
769 assert!(v2.supports_message_id(SnapMessageId::BlockAccessLists as u8));
770 assert!(!v2.supports_message_id(0x0a));
771 assert!(!v2.supports_message_id(0xff));
772 }
773
774 #[test_case(
775 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
776 request_id: 1, root_hash: B256::ZERO, starting_hash: B256::ZERO,
777 limit_hash: B256::ZERO, response_bytes: 0,
778 }), 1, false ; "get_account_range is a request"
779 )]
780 #[test_case(
781 SnapProtocolMessage::AccountRange(AccountRangeMessage {
782 request_id: 2, accounts: vec![], proof: vec![],
783 }), 2, true ; "account_range is a response"
784 )]
785 #[test_case(
786 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
787 request_id: 3, root_hash: B256::ZERO, account_hashes: vec![],
788 starting_hash: B256::ZERO.into(), limit_hash: B256::ZERO.into(), response_bytes: 0,
789 }), 3, false ; "get_storage_ranges is a request"
790 )]
791 #[test_case(
792 SnapProtocolMessage::StorageRanges(StorageRangesMessage {
793 request_id: 4, slots: vec![], proof: vec![],
794 }), 4, true ; "storage_ranges is a response"
795 )]
796 #[test_case(
797 SnapProtocolMessage::GetByteCodes(GetByteCodesMessage {
798 request_id: 5, hashes: vec![], response_bytes: 0,
799 }), 5, false ; "get_byte_codes is a request"
800 )]
801 #[test_case(
802 SnapProtocolMessage::ByteCodes(ByteCodesMessage { request_id: 6, codes: vec![] }),
803 6, true ; "byte_codes is a response"
804 )]
805 #[test_case(
806 SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
807 request_id: 7, block_hashes: vec![], response_bytes: 0,
808 }), 7, false ; "get_block_access_lists is a request"
809 )]
810 #[test_case(
811 SnapProtocolMessage::BlockAccessLists(BlockAccessListsMessage {
812 request_id: 8, block_access_lists: BlockAccessLists(vec![]),
813 }), 8, true ; "block_access_lists is a response"
814 )]
815 fn request_id_and_is_response(msg: SnapProtocolMessage, expected_id: u64, is_response: bool) {
816 assert_eq!(msg.request_id(), expected_id);
817 assert_eq!(msg.is_response(), is_response);
818 }
819
820 #[test_case(
821 SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
822 request_id: 1, root_hash: B256::ZERO, starting_hash: B256::ZERO,
823 limit_hash: B256::ZERO, response_bytes: 0,
824 }) ; "get_account_range"
825 )]
826 #[test_case(
827 SnapProtocolMessage::AccountRange(AccountRangeMessage {
828 request_id: 1, accounts: vec![], proof: vec![],
829 }) ; "account_range"
830 )]
831 #[test_case(
832 SnapProtocolMessage::GetStorageRanges(GetStorageRangesMessage {
833 request_id: 1, root_hash: B256::ZERO, account_hashes: vec![],
834 starting_hash: B256::ZERO.into(), limit_hash: B256::ZERO.into(), response_bytes: 0,
835 }) ; "get_storage_ranges"
836 )]
837 #[test_case(
838 SnapProtocolMessage::StorageRanges(StorageRangesMessage {
839 request_id: 1, slots: vec![], proof: vec![],
840 }) ; "storage_ranges"
841 )]
842 #[test_case(
843 SnapProtocolMessage::GetByteCodes(GetByteCodesMessage {
844 request_id: 1, hashes: vec![], response_bytes: 0,
845 }) ; "get_byte_codes"
846 )]
847 #[test_case(
848 SnapProtocolMessage::ByteCodes(ByteCodesMessage { request_id: 1, codes: vec![] }) ;
849 "byte_codes"
850 )]
851 #[test_case(
852 SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
853 request_id: 1, block_hashes: vec![], response_bytes: 0,
854 }) ; "get_block_access_lists"
855 )]
856 #[test_case(
857 SnapProtocolMessage::BlockAccessLists(BlockAccessListsMessage {
858 request_id: 1, block_access_lists: BlockAccessLists(vec![]),
859 }) ; "block_access_lists"
860 )]
861 fn per_variant_request_id_and_round_trip(mut msg: SnapProtocolMessage) {
862 msg.set_request_id(42);
864 assert_eq!(msg.request_id(), 42);
865
866 let decoded =
868 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &msg.encode()).unwrap();
869 assert_eq!(decoded, msg);
870 }
871
872 #[test]
873 fn decode_versioned_rejects_empty() {
874 assert!(matches!(
876 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &[]),
877 Err(SnapProtocolError::Empty)
878 ));
879 }
880
881 #[test]
882 fn decode_versioned_rejects_trie_node_ids_in_v2() {
883 for id in [0x06u8, 0x07] {
886 assert!(matches!(
887 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &[id]),
888 Err(SnapProtocolError::UnsupportedMessageId(got, SnapVersion::V2)) if got == id
889 ));
890 }
891 }
892
893 #[test]
894 fn decode_versioned_reports_malformed_body() {
895 assert!(matches!(
898 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &[0x08, 0xff]),
899 Err(SnapProtocolError::Rlp(_))
900 ));
901 }
902
903 #[test]
904 fn decode_versioned_rejects_trailing_bytes() {
905 let original = SnapProtocolMessage::GetBlockAccessLists(GetBlockAccessListsMessage {
908 request_id: 7,
909 block_hashes: vec![b256_from_u64(1)],
910 response_bytes: 1024,
911 });
912 let mut framed = original.encode().to_vec();
913 framed.push(0xff);
914 assert!(matches!(
915 SnapProtocolMessage::decode_versioned(SnapVersion::V2, &framed),
916 Err(SnapProtocolError::Rlp(alloy_rlp::Error::UnexpectedLength))
917 ));
918 }
919
920 #[test]
921 fn get_storage_ranges_decodes_geths_empty_origin_and_limit() {
922 let body = alloy_rlp::encode(GethStorageRequest {
923 request_id: 21,
924 root_hash: B256::ZERO,
925 account_hashes: vec![B256::repeat_byte(1), B256::repeat_byte(2)],
926 origin: Bytes::new(),
927 limit: Bytes::new(),
928 response_bytes: 1024,
929 });
930 let mut framed = vec![SnapMessageId::GetStorageRanges as u8];
931 framed.extend_from_slice(&body);
932
933 let decoded = SnapProtocolMessage::decode_versioned(SnapVersion::V2, &framed).unwrap();
934 let SnapProtocolMessage::GetStorageRanges(msg) = decoded else {
935 panic!("expected a GetStorageRanges message");
936 };
937 assert_eq!(msg.starting_hash.unwrap_or(B256::ZERO), B256::ZERO);
938 assert_eq!(msg.limit_hash.unwrap_or(B256::repeat_byte(0xff)), B256::repeat_byte(0xff));
939 }
940
941 fn trie_account(storage_root: B256, code_hash: B256) -> TrieAccount {
942 TrieAccount::new(7, U256::from(42), storage_root, code_hash)
943 }
944
945 #[test]
946 fn account_range_matches_nested_account_wire_encoding() {
947 let wire = alloy_primitives::hex!(
949 "ea01e7e6a00101010101010101010101010101010101010101010101010101010101010101c4072a8080c0"
950 );
951 let message = AccountRangeMessage {
952 request_id: 1,
953 accounts: vec![AccountData::from_trie_account(
954 B256::repeat_byte(1),
955 &trie_account(EMPTY_ROOT_HASH, KECCAK256_EMPTY),
956 )],
957 proof: vec![],
958 };
959
960 assert_eq!(alloy_rlp::encode(&message), wire);
961 assert_eq!(alloy_rlp::decode_exact::<AccountRangeMessage>(&wire).unwrap(), message);
962 }
963
964 #[test]
965 fn account_data_rejects_byte_string_wrapped_body() {
966 let wire = alloy_primitives::hex!(
967 "e7a0010101010101010101010101010101010101010101010101010101010101010185c4072a8080"
968 );
969 assert!(alloy_rlp::decode_exact::<AccountData>(&wire).is_err());
970 }
971
972 #[test]
973 fn account_data_consumes_only_its_own_list() {
974 let account = AccountData::from_trie_account(
975 B256::repeat_byte(1),
976 &trie_account(B256::repeat_byte(2), B256::repeat_byte(3)),
977 );
978 let mut wire = alloy_rlp::encode(&account);
979 assert_eq!(wire.len(), account.length());
980 wire.push(0x80);
981 let mut input = wire.as_slice();
982 assert_eq!(AccountData::decode(&mut input).unwrap(), account);
983 assert_eq!(input, &[0x80]);
984 }
985
986 #[test]
987 fn account_data_rejects_missing_truncated_and_extra_body_items() {
988 for wire in [
989 "e1a00101010101010101010101010101010101010101010101010101010101010101",
990 "e4a00101010101010101010101010101010101010101010101010101010101010101c4072a",
991 "e7a00101010101010101010101010101010101010101010101010101010101010101c4072a808080",
992 ] {
993 let bytes = alloy_primitives::hex::decode(wire).unwrap();
994 assert!(alloy_rlp::decode_exact::<AccountData>(&bytes).is_err());
995 }
996 }
997
998 #[test]
999 fn slim_body_elides_empty_storage_and_code() {
1000 let account = trie_account(EMPTY_ROOT_HASH, KECCAK256_EMPTY);
1001 let hash = B256::repeat_byte(1);
1002 let encoded = AccountData::from_trie_account(hash, &account);
1003
1004 let body = alloy_rlp::encode(&encoded.body);
1005 assert_eq!(body, alloy_primitives::hex!("c4072a8080"));
1006 assert_eq!(alloy_rlp::decode_exact::<SlimAccountBody>(&body).unwrap(), encoded.body);
1007 assert_eq!(encoded.trie_account(), account);
1008 assert_eq!(encoded.into_trie_entry(), (hash, account));
1009 }
1010
1011 #[test]
1012 #[allow(clippy::clone_on_copy)]
1013 fn slim_body_keeps_non_default_storage_and_code() {
1014 let account = trie_account(B256::repeat_byte(2), B256::repeat_byte(3));
1015 let encoded = AccountData::from_trie_account(B256::repeat_byte(1), &account);
1016
1017 let body = alloy_rlp::encode(&encoded.body);
1018 assert_eq!(body, alloy_rlp::encode(account.clone()));
1019 assert_eq!(alloy_rlp::decode_exact::<SlimAccountBody>(&body).unwrap(), encoded.body);
1020 assert_eq!(encoded.trie_account(), account);
1021 }
1022
1023 #[test_case(1)]
1024 #[test_case(16)]
1025 #[test_case(31)]
1026 #[test_case(33)]
1027 fn slim_body_rejects_invalid_hash_lengths(len: usize) {
1028 let invalid = vec![0xaa; len];
1029 for (storage_root, code_hash) in
1030 [(invalid.as_slice(), &[][..]), (&[][..], invalid.as_slice())]
1031 {
1032 let body = alloy_rlp::encode(SlimAccountBodyRef {
1033 nonce: 7,
1034 balance: U256::from(42),
1035 storage_root,
1036 code_hash,
1037 });
1038 assert_eq!(
1039 alloy_rlp::decode_exact::<SlimAccountBody>(&body),
1040 Err(alloy_rlp::Error::UnexpectedLength)
1041 );
1042 }
1043 }
1044
1045 #[test_case("c0")]
1046 #[test_case("c3072a80")]
1047 #[test_case("c5072a808080")]
1048 #[test_case("c4072a808000")]
1049 fn slim_body_rejects_missing_and_extra_fields(wire: &str) {
1050 let bytes = alloy_primitives::hex::decode(wire).unwrap();
1051 assert!(alloy_rlp::decode_exact::<SlimAccountBody>(&bytes).is_err());
1052 }
1053
1054 #[test]
1055 fn storage_data_rejects_trailing_bytes() {
1056 let mut slot = StorageData::from_value(B256::repeat_byte(4), U256::ONE);
1057 slot.data = [slot.data.as_ref(), &[0x00]].concat().into();
1058
1059 assert!(slot.value().is_err());
1060 }
1061}