snarkos_node_router_messages/
peer_response.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18use snarkvm::prelude::{FromBytes, ToBytes};
19
20use std::borrow::Cow;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct PeerResponse {
24    pub peers: Vec<(SocketAddr, Option<u32>)>,
25}
26
27impl MessageTrait for PeerResponse {
28    /// Returns the message name.
29    #[inline]
30    fn name(&self) -> Cow<'static, str> {
31        "PeerResponse".into()
32    }
33}
34
35impl ToBytes for PeerResponse {
36    fn write_le<W: io::Write>(&self, mut writer: W) -> io::Result<()> {
37        // Return error if the number of peers exceeds the maximum.
38        if self.peers.len() > u8::MAX as usize {
39            return Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Too many peers: {}", self.peers.len())));
40        }
41
42        // A version indicator; we don't expect empty peer responses, so a zero value can serve
43        // as an indicator that this message is to be processed differently. The version value
44        // can be changed to a 2 in the future, once everyone expects it there.
45        0u8.write_le(&mut writer)?;
46
47        (self.peers.len() as u8).write_le(&mut writer)?;
48        for (addr, height) in self.peers.iter() {
49            addr.write_le(&mut writer)?;
50            if let Some(h) = height {
51                1u8.write_le(&mut writer)?;
52                h.write_le(&mut writer)?;
53            } else {
54                0u8.write_le(&mut writer)?;
55            }
56        }
57        Ok(())
58    }
59}
60
61impl FromBytes for PeerResponse {
62    fn read_le<R: io::Read>(mut reader: R) -> io::Result<Self> {
63        // Read the peer count if their heights aren't present; otherwise, interpret this value
64        // as the message version. It is a workaround for a currently missing version value.
65        // The worst-case scenario is if a node hasn't updated, and it gets a `PeerRequest` from
66        // its only peer who has; this would cause it to return a message that appears as if it
67        // contains heights (due to a leading `0`), but it would end up failing to deserialize.
68        // TODO: after a release or two, we should always be expecting the version to be present,
69        // simplifying the deserialization; also, remove the `empty_old_peerlist_handling` test.
70        let mut contains_heights = false;
71        let count_or_version = u8::read_le(&mut reader)?;
72        let count = if count_or_version == 0 {
73            // Version indicator found; this message will contain optional heights.
74            contains_heights = true;
75            // If the first value is a zero, the next u8 is the peer count.
76            u8::read_le(&mut reader)?
77        } else {
78            // A non-zero value indicates that this is the "old" PeerResponse without heights.
79            count_or_version
80        };
81
82        let mut peers = Vec::with_capacity(count as usize);
83        for _ in 0..count {
84            let addr = SocketAddr::read_le(&mut reader)?;
85            let height = if contains_heights {
86                match u8::read_le(&mut reader)? {
87                    1 => Some(u32::read_le(&mut reader)?),
88                    0 => None,
89                    _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid peer height".to_string())),
90                }
91            } else {
92                None
93            };
94            peers.push((addr, height));
95        }
96
97        Ok(Self { peers })
98    }
99}
100
101#[cfg(test)]
102pub mod prop_tests {
103    use crate::PeerResponse;
104    use snarkvm::utilities::{FromBytes, ToBytes};
105
106    use bytes::{Buf, BufMut, BytesMut};
107    use proptest::{
108        collection::vec,
109        prelude::{BoxedStrategy, Strategy, any},
110    };
111    use std::{
112        io,
113        net::{IpAddr, SocketAddr},
114    };
115    use test_strategy::proptest;
116
117    pub fn any_valid_socket_addr() -> BoxedStrategy<(SocketAddr, Option<u32>)> {
118        any::<(IpAddr, u16, Option<u32>)>()
119            .prop_map(|(ip_addr, port, height)| (SocketAddr::new(ip_addr, port), height))
120            .boxed()
121    }
122
123    pub fn any_vec() -> BoxedStrategy<Vec<(SocketAddr, Option<u32>)>> {
124        vec(any_valid_socket_addr(), 0..50).prop_map(|v| v).boxed()
125    }
126
127    pub fn any_peer_response() -> BoxedStrategy<PeerResponse> {
128        any_vec().prop_map(|peers| PeerResponse { peers }).boxed()
129    }
130
131    #[proptest]
132    fn peer_response_roundtrip(#[strategy(any_peer_response())] peer_response: PeerResponse) {
133        let mut bytes = BytesMut::default().writer();
134        peer_response.write_le(&mut bytes).unwrap();
135        let decoded = PeerResponse::read_le(&mut bytes.into_inner().reader()).unwrap();
136        assert_eq!(decoded, peer_response);
137    }
138
139    // The following test will be obsolete once all the nodes handle heights in the `PeerResponse`.
140    #[test]
141    fn empty_old_peerlist_handling() {
142        // An empty `PeerResponse` without heights contains a single 0u8.
143        let serialized = &[0u8];
144        let deserialized = PeerResponse::read_le(&serialized[..]).unwrap_err();
145        // Check for the expected error.
146        assert_eq!(deserialized.kind(), io::ErrorKind::UnexpectedEof);
147    }
148}