snarkos_node_bft_events/
block_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
18#[derive(Clone, PartialEq, Eq)]
19pub struct BlockResponse<N: Network> {
20    /// The original block request.
21    pub request: BlockRequest,
22    /// The blocks.
23    pub blocks: Data<DataBlocks<N>>,
24}
25
26impl<N: Network> EventTrait for BlockResponse<N> {
27    /// Returns the event name.
28    #[inline]
29    fn name(&self) -> Cow<'static, str> {
30        let start = self.request.start_height;
31        let end = self.request.end_height;
32        match start + 1 == end {
33            true => format!("BlockResponse {start}"),
34            false => format!("BlockResponse {start}..{end}"),
35        }
36        .into()
37    }
38}
39
40impl<N: Network> ToBytes for BlockResponse<N> {
41    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
42        self.request.write_le(&mut writer)?;
43        self.blocks.write_le(&mut writer)
44    }
45}
46
47impl<N: Network> FromBytes for BlockResponse<N> {
48    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
49        let request = BlockRequest::read_le(&mut reader)?;
50        let blocks = Data::read_le(&mut reader)?;
51
52        Ok(Self { request, blocks })
53    }
54}
55
56impl<N: Network> std::fmt::Debug for BlockResponse<N> {
57    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
58        write!(f, "{}", self.name())
59    }
60}
61
62/// A wrapper for a list of blocks.
63#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
64pub struct DataBlocks<N: Network>(pub Vec<Block<N>>);
65
66impl<N: Network> DataBlocks<N> {
67    /// The maximum number of blocks that can be sent in a single message.
68    pub const MAXIMUM_NUMBER_OF_BLOCKS: u8 = 5;
69
70    /// Ensures that the blocks are well-formed in a block response.
71    pub fn ensure_response_is_well_formed(
72        &self,
73        peer_ip: SocketAddr,
74        start_height: u32,
75        end_height: u32,
76    ) -> Result<()> {
77        // Ensure the blocks are not empty.
78        ensure!(!self.0.is_empty(), "Peer '{peer_ip}' sent an empty block response ({start_height}..{end_height})");
79        // Check that the blocks are sequentially ordered.
80        if !self.0.windows(2).all(|w| w[0].height() + 1 == w[1].height()) {
81            bail!("Peer '{peer_ip}' sent an invalid block response (blocks are not sequentially ordered)")
82        }
83
84        // Retrieve the start (inclusive) and end (exclusive) block height.
85        let candidate_start_height = self.first().map(|b| b.height()).unwrap_or(0);
86        let candidate_end_height = 1 + self.last().map(|b| b.height()).unwrap_or(0);
87        // Check that the range matches the block request.
88        if start_height != candidate_start_height || end_height != candidate_end_height {
89            bail!("Peer '{peer_ip}' sent an invalid block response (range does not match block request)")
90        }
91        Ok(())
92    }
93}
94
95impl<N: Network> std::ops::Deref for DataBlocks<N> {
96    type Target = Vec<Block<N>>;
97
98    /// Returns the list of blocks.
99    fn deref(&self) -> &Self::Target {
100        &self.0
101    }
102}
103
104impl<N: Network> ToBytes for DataBlocks<N> {
105    /// Writes the blocks to the given writer.
106    #[inline]
107    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
108        // Prepare the number of blocks.
109        let num_blocks = self.0.len() as u8;
110        // Ensure that the number of blocks is within the allowed range.
111        if num_blocks > Self::MAXIMUM_NUMBER_OF_BLOCKS {
112            return Err(error("Block response exceeds maximum number of blocks"));
113        }
114        // Write the number of blocks.
115        num_blocks.write_le(&mut writer)?;
116        // Write the blocks.
117        self.0.iter().take(num_blocks as usize).try_for_each(|block| block.write_le(&mut writer))
118    }
119}
120
121impl<N: Network> FromBytes for DataBlocks<N> {
122    /// Reads the message from the given reader.
123    #[inline]
124    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
125        // Read the number of blocks.
126        let num_blocks = u8::read_le(&mut reader)?;
127        // Ensure that the number of blocks is within the allowed range.
128        if num_blocks > Self::MAXIMUM_NUMBER_OF_BLOCKS {
129            return Err(error("Block response exceeds maximum number of blocks"));
130        }
131        // Read the blocks.
132        let blocks = (0..num_blocks).map(|_| Block::read_le(&mut reader)).collect::<Result<Vec<_>, _>>()?;
133        Ok(Self(blocks))
134    }
135}
136
137#[cfg(test)]
138pub mod prop_tests {
139    use crate::{BlockResponse, DataBlocks, block_request::prop_tests::any_block_request};
140    use snarkvm::{
141        ledger::snarkvm_ledger_test_helpers::sample_genesis_block,
142        prelude::{FromBytes, TestRng, ToBytes, block::Block, narwhal::Data},
143    };
144
145    use bytes::{Buf, BufMut, BytesMut};
146    use proptest::{
147        collection::vec,
148        prelude::{BoxedStrategy, Strategy, any},
149    };
150    use test_strategy::proptest;
151
152    type CurrentNetwork = snarkvm::prelude::MainnetV0;
153
154    pub fn any_block() -> BoxedStrategy<Block<CurrentNetwork>> {
155        any::<u64>().prop_map(|seed| sample_genesis_block(&mut TestRng::fixed(seed))).boxed()
156    }
157
158    pub fn any_data_blocks() -> BoxedStrategy<DataBlocks<CurrentNetwork>> {
159        vec(any_block(), 0..=1).prop_map(DataBlocks).boxed()
160    }
161
162    pub fn any_block_response() -> BoxedStrategy<BlockResponse<CurrentNetwork>> {
163        (any_block_request(), any_data_blocks())
164            .prop_map(|(request, data_blocks)| BlockResponse { request, blocks: Data::Object(data_blocks) })
165            .boxed()
166    }
167
168    #[proptest]
169    fn block_response_roundtrip(#[strategy(any_block_response())] block_response: BlockResponse<CurrentNetwork>) {
170        let mut bytes = BytesMut::default().writer();
171        block_response.write_le(&mut bytes).unwrap();
172        let decoded = BlockResponse::<CurrentNetwork>::read_le(&mut bytes.into_inner().reader()).unwrap();
173        assert_eq!(block_response.request, decoded.request);
174        assert_eq!(
175            block_response.blocks.deserialize_blocking().unwrap(),
176            decoded.blocks.deserialize_blocking().unwrap(),
177        );
178    }
179}