/__w/smoldot/smoldot/repo/lib/src/network/codec/block_request.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | use crate::util::protobuf; |
19 | | |
20 | | use alloc::{borrow::ToOwned as _, vec::Vec}; |
21 | | use core::num::NonZeroU32; |
22 | | |
23 | | /// Description of a block request that can be sent to a peer. |
24 | | #[derive(Debug, Clone, PartialEq, Eq)] |
25 | | pub struct BlocksRequestConfig { |
26 | | /// First block that the remote must return. |
27 | | pub start: BlocksRequestConfigStart, |
28 | | /// Number of blocks to request. The remote is free to return fewer blocks than requested. |
29 | | pub desired_count: NonZeroU32, |
30 | | /// Whether the first block should be the one with the highest number, of the one with the |
31 | | /// lowest number. |
32 | | pub direction: BlocksRequestDirection, |
33 | | /// Which fields should be present in the response. |
34 | | pub fields: BlocksRequestFields, |
35 | | } |
36 | | |
37 | | /// Whether the first block should be the one with the highest number, of the one with the lowest |
38 | | /// number. |
39 | | #[derive(Debug, Clone, PartialEq, Eq)] |
40 | | pub enum BlocksRequestDirection { |
41 | | /// Blocks should be returned in ascending number, starting from the requested one. In other |
42 | | /// words, amongst all the blocks in the response, the requested block must be the one with |
43 | | /// the lowest block number, and all the other blocks are its descendants. |
44 | | Ascending, |
45 | | /// Blocks should be returned in descending number, starting from the requested one. In other |
46 | | /// words, amongst all the blocks in the response, the requested block must be the one with |
47 | | /// the highest block number, and all the other blocks are its ancestors. |
48 | | Descending, |
49 | | } |
50 | | |
51 | | /// Which fields should be present in the response. |
52 | | #[derive(Debug, Clone, PartialEq, Eq)] |
53 | | pub struct BlocksRequestFields { |
54 | | pub header: bool, |
55 | | pub body: bool, |
56 | | pub justifications: bool, |
57 | | } |
58 | | |
59 | | /// Which block the remote must return first. |
60 | | #[derive(Debug, Clone, PartialEq, Eq)] |
61 | | pub enum BlocksRequestConfigStart { |
62 | | /// Hash of the block. |
63 | | Hash([u8; 32]), |
64 | | /// Number of the block, where 0 would be the genesis block. |
65 | | Number(u64), |
66 | | } |
67 | | |
68 | | // See https://github.com/paritytech/substrate/blob/c8653447fc8ef8d95a92fe164c96dffb37919e85/client/network/sync/src/schema/api.v1.proto |
69 | | // for protocol definition. |
70 | | |
71 | | /// Builds the bytes corresponding to a block request. |
72 | 0 | pub fn build_block_request( |
73 | 0 | block_number_bytes: usize, |
74 | 0 | config: &BlocksRequestConfig, |
75 | 0 | ) -> impl Iterator<Item = impl AsRef<[u8]>> { |
76 | 0 | let mut fields = 0u32; |
77 | 0 | if config.fields.header { |
78 | 0 | fields |= 1 << 24; |
79 | 0 | } |
80 | 0 | if config.fields.body { |
81 | 0 | fields |= 1 << 25; |
82 | 0 | } |
83 | 0 | if config.fields.justifications { |
84 | 0 | fields |= 1 << 28; |
85 | 0 | } |
86 | | |
87 | 0 | let from_block = match config.start { |
88 | 0 | BlocksRequestConfigStart::Hash(h) => { |
89 | 0 | either::Left(protobuf::bytes_tag_encode(2, h).map(either::Left)) |
90 | | } |
91 | 0 | BlocksRequestConfigStart::Number(n) => { |
92 | 0 | let mut data = Vec::with_capacity(block_number_bytes); |
93 | 0 | data.extend_from_slice(&n.to_le_bytes()); |
94 | 0 | // TODO: unclear what to do if the block number doesn't fit within `expected_block_number_bytes` |
95 | 0 | data.resize(block_number_bytes, 0); |
96 | 0 | either::Right(protobuf::bytes_tag_encode(3, data).map(either::Right)) |
97 | | } |
98 | | }; |
99 | | |
100 | 0 | protobuf::uint32_tag_encode(1, fields) |
101 | 0 | .map(either::Left) |
102 | 0 | .map(either::Left) |
103 | 0 | .map(either::Left) |
104 | 0 | .chain( |
105 | 0 | from_block |
106 | 0 | .map(either::Right) |
107 | 0 | .map(either::Left) |
108 | 0 | .map(either::Left), |
109 | 0 | ) |
110 | 0 | .chain( |
111 | 0 | protobuf::enum_tag_encode( |
112 | 0 | 5, |
113 | 0 | match config.direction { |
114 | 0 | BlocksRequestDirection::Ascending => 0, |
115 | 0 | BlocksRequestDirection::Descending => 1, |
116 | | }, |
117 | | ) |
118 | 0 | .map(either::Left) |
119 | 0 | .map(either::Right) |
120 | 0 | .map(either::Left), |
121 | 0 | ) |
122 | 0 | .chain( |
123 | 0 | protobuf::uint32_tag_encode(6, config.desired_count.get()) |
124 | 0 | .map(either::Right) |
125 | 0 | .map(either::Right) |
126 | 0 | .map(either::Left), |
127 | 0 | ) |
128 | 0 | // The `support_multiple_justifications` flag indicates that we support responses |
129 | 0 | // containing multiple justifications. This flag is simply a way to maintain backwards |
130 | 0 | // compatibility in the protocol. |
131 | 0 | .chain(protobuf::bool_tag_encode(7, true).map(either::Right)) |
132 | 0 | } Unexecuted instantiation: _RNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request19build_block_request Unexecuted instantiation: _RNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request19build_block_request |
133 | | |
134 | | /// Decodes a blocks request. |
135 | | // TODO: should have a more zero-cost API, but we're limited by the protobuf library for that |
136 | 2 | pub fn decode_block_request( |
137 | 2 | expected_block_number_bytes: usize, |
138 | 2 | request_bytes: &[u8], |
139 | 2 | ) -> Result<BlocksRequestConfig, DecodeBlockRequestError> { |
140 | 2 | let mut parser = nom::combinator::all_consuming::<_, _, nom::error::Error<&[u8]>, _>( |
141 | 2 | nom::combinator::complete(protobuf::message_decode! { |
142 | 0 | #[required] fields = 1 => protobuf::uint32_tag_decode, |
143 | 0 | #[optional] hash = 2 => protobuf::bytes_tag_decode, |
144 | 0 | #[optional] number = 3 => protobuf::bytes_tag_decode, |
145 | 0 | #[optional] direction = 5 => protobuf::enum_tag_decode, |
146 | 0 | #[optional] max_blocks = 6 => protobuf::uint32_tag_decode, |
147 | 2 | }), Unexecuted instantiation: _RNCNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20decode_block_requests_00Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20decode_block_requests_00Bb_ |
148 | 2 | ); |
149 | | |
150 | 2 | let decoded1 = match nom::Finish::finish(parser(request_bytes)) { |
151 | 1 | Ok((_, rq)) => rq, |
152 | 1 | Err(_) => return Err(DecodeBlockRequestError::ProtobufDecode), |
153 | | }; |
154 | | |
155 | | Ok(BlocksRequestConfig { |
156 | 1 | start: match (decoded.hash, decoded.number) { |
157 | 0 | (Some(h), None) => BlocksRequestConfigStart::Hash( |
158 | 0 | <[u8; 32]>::try_from(h) |
159 | 0 | .map_err(|_| DecodeBlockRequestError::InvalidBlockHashLength)?, Unexecuted instantiation: _RNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20decode_block_request0B9_ Unexecuted instantiation: _RNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20decode_block_request0B9_ |
160 | | ), |
161 | 1 | (None, Some(n)) => { |
162 | 1 | if n.len() != expected_block_number_bytes { |
163 | 0 | return Err(DecodeBlockRequestError::InvalidBlockNumber); |
164 | 1 | } |
165 | 1 | |
166 | 1 | // The exact format is the SCALE encoding of a block number. |
167 | 1 | // The block number can have a varying number of bytes, and it is therefore |
168 | 1 | // not really possible to know how many bytes to expect here. |
169 | 1 | // Because the SCALE encoding of a number is the number in little endian format, |
170 | 1 | // we decode the bytes in little endian format in a way that works no matter the |
171 | 1 | // number of bytes. |
172 | 1 | let mut num = 0u64; |
173 | 1 | let mut shift = 0u32; |
174 | 5 | for byte4 in n { |
175 | 4 | let shifted = u64::from(*byte) |
176 | 4 | .checked_mul(1 << shift) |
177 | 4 | .ok_or(DecodeBlockRequestError::InvalidBlockNumber)?0 ; |
178 | 4 | num = num |
179 | 4 | .checked_add(shifted) |
180 | 4 | .ok_or(DecodeBlockRequestError::InvalidBlockNumber)?0 ; |
181 | 4 | shift = shift |
182 | 4 | .checked_add(8) |
183 | 4 | .ok_or(DecodeBlockRequestError::InvalidBlockNumber)?0 ; |
184 | | } |
185 | | |
186 | 1 | BlocksRequestConfigStart::Number(num) |
187 | | } |
188 | 0 | (Some(_), Some(_)) => return Err(DecodeBlockRequestError::ProtobufDecode), |
189 | 0 | (None, None) => return Err(DecodeBlockRequestError::MissingStartBlock), |
190 | | }, |
191 | | desired_count: { |
192 | | // A missing field or a `0` field are both interpreted as "no limit". |
193 | 1 | NonZeroU32::new(decoded.max_blocks.unwrap_or(u32::MAX)) |
194 | 1 | .unwrap_or(NonZeroU32::new(u32::MAX).unwrap()) |
195 | 1 | }, |
196 | 1 | direction: match decoded.direction { |
197 | 1 | None | Some(0) => BlocksRequestDirection::Ascending, |
198 | 0 | Some(1) => BlocksRequestDirection::Descending, |
199 | 0 | Some(_) => return Err(DecodeBlockRequestError::InvalidDirection), |
200 | | }, |
201 | | fields: { |
202 | 1 | if (decoded.fields & !(1 << 24 | 1 << 25 | 1 << 28)) != 0 { |
203 | 0 | return Err(DecodeBlockRequestError::UnknownFieldBits); |
204 | 1 | } |
205 | 1 | BlocksRequestFields { |
206 | 1 | header: (decoded.fields & (1 << 24)) != 0, |
207 | 1 | body: (decoded.fields & (1 << 25)) != 0, |
208 | 1 | justifications: (decoded.fields & (1 << 28)) != 0, |
209 | 1 | } |
210 | | }, |
211 | | }) |
212 | 2 | } _RNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20decode_block_request Line | Count | Source | 136 | 2 | pub fn decode_block_request( | 137 | 2 | expected_block_number_bytes: usize, | 138 | 2 | request_bytes: &[u8], | 139 | 2 | ) -> Result<BlocksRequestConfig, DecodeBlockRequestError> { | 140 | 2 | let mut parser = nom::combinator::all_consuming::<_, _, nom::error::Error<&[u8]>, _>( | 141 | 2 | nom::combinator::complete(protobuf::message_decode! { | 142 | | #[required] fields = 1 => protobuf::uint32_tag_decode, | 143 | | #[optional] hash = 2 => protobuf::bytes_tag_decode, | 144 | | #[optional] number = 3 => protobuf::bytes_tag_decode, | 145 | | #[optional] direction = 5 => protobuf::enum_tag_decode, | 146 | | #[optional] max_blocks = 6 => protobuf::uint32_tag_decode, | 147 | 2 | }), | 148 | 2 | ); | 149 | | | 150 | 2 | let decoded1 = match nom::Finish::finish(parser(request_bytes)) { | 151 | 1 | Ok((_, rq)) => rq, | 152 | 1 | Err(_) => return Err(DecodeBlockRequestError::ProtobufDecode), | 153 | | }; | 154 | | | 155 | | Ok(BlocksRequestConfig { | 156 | 1 | start: match (decoded.hash, decoded.number) { | 157 | 0 | (Some(h), None) => BlocksRequestConfigStart::Hash( | 158 | 0 | <[u8; 32]>::try_from(h) | 159 | 0 | .map_err(|_| DecodeBlockRequestError::InvalidBlockHashLength)?, | 160 | | ), | 161 | 1 | (None, Some(n)) => { | 162 | 1 | if n.len() != expected_block_number_bytes { | 163 | 0 | return Err(DecodeBlockRequestError::InvalidBlockNumber); | 164 | 1 | } | 165 | 1 | | 166 | 1 | // The exact format is the SCALE encoding of a block number. | 167 | 1 | // The block number can have a varying number of bytes, and it is therefore | 168 | 1 | // not really possible to know how many bytes to expect here. | 169 | 1 | // Because the SCALE encoding of a number is the number in little endian format, | 170 | 1 | // we decode the bytes in little endian format in a way that works no matter the | 171 | 1 | // number of bytes. | 172 | 1 | let mut num = 0u64; | 173 | 1 | let mut shift = 0u32; | 174 | 5 | for byte4 in n { | 175 | 4 | let shifted = u64::from(*byte) | 176 | 4 | .checked_mul(1 << shift) | 177 | 4 | .ok_or(DecodeBlockRequestError::InvalidBlockNumber)?0 ; | 178 | 4 | num = num | 179 | 4 | .checked_add(shifted) | 180 | 4 | .ok_or(DecodeBlockRequestError::InvalidBlockNumber)?0 ; | 181 | 4 | shift = shift | 182 | 4 | .checked_add(8) | 183 | 4 | .ok_or(DecodeBlockRequestError::InvalidBlockNumber)?0 ; | 184 | | } | 185 | | | 186 | 1 | BlocksRequestConfigStart::Number(num) | 187 | | } | 188 | 0 | (Some(_), Some(_)) => return Err(DecodeBlockRequestError::ProtobufDecode), | 189 | 0 | (None, None) => return Err(DecodeBlockRequestError::MissingStartBlock), | 190 | | }, | 191 | | desired_count: { | 192 | | // A missing field or a `0` field are both interpreted as "no limit". | 193 | 1 | NonZeroU32::new(decoded.max_blocks.unwrap_or(u32::MAX)) | 194 | 1 | .unwrap_or(NonZeroU32::new(u32::MAX).unwrap()) | 195 | 1 | }, | 196 | 1 | direction: match decoded.direction { | 197 | 1 | None | Some(0) => BlocksRequestDirection::Ascending, | 198 | 0 | Some(1) => BlocksRequestDirection::Descending, | 199 | 0 | Some(_) => return Err(DecodeBlockRequestError::InvalidDirection), | 200 | | }, | 201 | | fields: { | 202 | 1 | if (decoded.fields & !(1 << 24 | 1 << 25 | 1 << 28)) != 0 { | 203 | 0 | return Err(DecodeBlockRequestError::UnknownFieldBits); | 204 | 1 | } | 205 | 1 | BlocksRequestFields { | 206 | 1 | header: (decoded.fields & (1 << 24)) != 0, | 207 | 1 | body: (decoded.fields & (1 << 25)) != 0, | 208 | 1 | justifications: (decoded.fields & (1 << 28)) != 0, | 209 | 1 | } | 210 | | }, | 211 | | }) | 212 | 2 | } |
Unexecuted instantiation: _RNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20decode_block_request |
213 | | |
214 | | /// Builds the bytes corresponding to a block response. |
215 | | // TODO: more zero-cost API |
216 | 0 | pub fn build_block_response(response: Vec<BlockData>) -> impl Iterator<Item = impl AsRef<[u8]>> { |
217 | 0 | // Note that this function assumes that `support_multiple_justifications` was true in the |
218 | 0 | // request. We intentionally don't support old versions where it was false. |
219 | 0 |
|
220 | 0 | response.into_iter().flat_map(|block| { |
221 | 0 | protobuf::message_tag_encode(1, { |
222 | 0 | let justifications = if let Some(justifications) = block.justifications { |
223 | 0 | let mut j = Vec::with_capacity( |
224 | 0 | 4 + justifications |
225 | 0 | .iter() |
226 | 0 | .fold(0, |sz, j| sz + 4 + 6 + j.justification.len()), Unexecuted instantiation: _RNCNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20build_block_response00Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response00Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response00CsiUjFBJteJ7x_17smoldot_full_node |
227 | 0 | ); |
228 | 0 | j.extend_from_slice( |
229 | 0 | crate::util::encode_scale_compact_usize(justifications.len()).as_ref(), |
230 | 0 | ); |
231 | 0 | for justification in &justifications { |
232 | 0 | j.extend_from_slice(&justification.engine_id); |
233 | 0 | j.extend_from_slice( |
234 | 0 | crate::util::encode_scale_compact_usize(justification.justification.len()) |
235 | 0 | .as_ref(), |
236 | 0 | ); |
237 | 0 | j.extend_from_slice(&justification.justification); |
238 | 0 | } |
239 | 0 | Some(j) |
240 | | } else { |
241 | 0 | None |
242 | | }; |
243 | | |
244 | 0 | protobuf::bytes_tag_encode(1, block.hash) |
245 | 0 | .map(either::Left) |
246 | 0 | .chain( |
247 | 0 | block |
248 | 0 | .header |
249 | 0 | .into_iter() |
250 | 0 | .flat_map(|h| protobuf::bytes_tag_encode(2, h)) Unexecuted instantiation: _RNCNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20build_block_response0s_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s_0CsiUjFBJteJ7x_17smoldot_full_node |
251 | 0 | .map(either::Right), |
252 | 0 | ) |
253 | 0 | .map(either::Left) |
254 | 0 | .chain( |
255 | 0 | block |
256 | 0 | .body |
257 | 0 | .into_iter() |
258 | 0 | .flat_map(|b| b.into_iter()) Unexecuted instantiation: _RNCNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20build_block_response0s0_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s0_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s0_0CsiUjFBJteJ7x_17smoldot_full_node |
259 | 0 | .flat_map(|tx| protobuf::bytes_tag_encode(3, tx)) Unexecuted instantiation: _RNCNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20build_block_response0s1_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s1_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s1_0CsiUjFBJteJ7x_17smoldot_full_node |
260 | 0 | .map(either::Left) |
261 | 0 | .chain( |
262 | 0 | justifications |
263 | 0 | .into_iter() |
264 | 0 | .flat_map(|j| protobuf::bytes_tag_encode(8, j)) Unexecuted instantiation: _RNCNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20build_block_response0s2_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s2_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0s2_0CsiUjFBJteJ7x_17smoldot_full_node |
265 | 0 | .map(either::Right), |
266 | 0 | ) |
267 | 0 | .map(either::Right), |
268 | 0 | ) |
269 | 0 | }) |
270 | 0 | }) Unexecuted instantiation: _RNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20build_block_response0B9_ Unexecuted instantiation: _RNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0B9_ Unexecuted instantiation: _RNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response0CsiUjFBJteJ7x_17smoldot_full_node |
271 | 0 | } Unexecuted instantiation: _RNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request20build_block_response Unexecuted instantiation: _RNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request20build_block_response |
272 | | |
273 | | /// Decodes a response to a block request. |
274 | | // TODO: should have a more zero-cost API |
275 | 1 | pub fn decode_block_response( |
276 | 1 | response_bytes: &[u8], |
277 | 1 | ) -> Result<Vec<BlockData>, DecodeBlockResponseError> { |
278 | 1 | let mut parser = nom::combinator::all_consuming::<_, _, nom::error::Error<&[u8]>, _>( |
279 | 1 | nom::combinator::complete(protobuf::message_decode! { |
280 | 1 | #[repeated(max = 32768)] blocks = 1 => protobuf::message_tag_decode(protobuf::message_decode!{ |
281 | 0 | #[required] hash = 1 => protobuf::bytes_tag_decode, |
282 | 0 | #[optional] header = 2 => protobuf::bytes_tag_decode, |
283 | 0 | #[repeated(max = usize::MAX)] body = 3 => protobuf::bytes_tag_decode, |
284 | 0 | #[optional] justifications = 8 => protobuf::bytes_tag_decode, |
285 | 1 | }), _RNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_block_responses0_0B9_ Line | Count | Source | 279 | 1 | nom::combinator::complete(protobuf::message_decode! { | 280 | 1 | #[repeated(max = 32768)] blocks = 1 => protobuf::message_tag_decode(protobuf::message_decode!{ | 281 | | #[required] hash = 1 => protobuf::bytes_tag_decode, | 282 | | #[optional] header = 2 => protobuf::bytes_tag_decode, | 283 | | #[repeated(max = usize::MAX)] body = 3 => protobuf::bytes_tag_decode, | 284 | | #[optional] justifications = 8 => protobuf::bytes_tag_decode, | 285 | 1 | }), |
Unexecuted instantiation: _RNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_block_responses0_0B9_ Unexecuted instantiation: _RNCNCNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_block_responses0_000Bd_ Unexecuted instantiation: _RNCNCNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_block_responses0_000Bd_ |
286 | 1 | }), |
287 | 1 | ); |
288 | | |
289 | 1 | let blocks0 = match nom::Finish::finish(parser(response_bytes)) { |
290 | 0 | Ok((_, out)) => out.blocks, |
291 | 1 | Err(_) => return Err(DecodeBlockResponseError::ProtobufDecode), |
292 | | }; |
293 | | |
294 | 0 | let mut blocks_out = Vec::with_capacity(blocks.len()); |
295 | 0 | for block in blocks { |
296 | 0 | if block.hash.len() != 32 { |
297 | 0 | return Err(DecodeBlockResponseError::InvalidHashLength); |
298 | 0 | } |
299 | 0 |
|
300 | 0 | blocks_out.push(BlockData { |
301 | 0 | hash: <[u8; 32]>::try_from(block.hash).unwrap(), |
302 | 0 | header: block.header.as_ref().map(|h| h.to_vec()), Unexecuted instantiation: _RNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_block_response0B9_ Unexecuted instantiation: _RNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_block_response0B9_ |
303 | 0 | // TODO: no; we might not have asked for the body |
304 | 0 | body: Some(block.body.into_iter().map(|tx| tx.to_vec()).collect()), Unexecuted instantiation: _RNCNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_block_responses_0B9_ Unexecuted instantiation: _RNCNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_block_responses_0B9_ |
305 | 0 | justifications: if let Some(justifications) = block.justifications { |
306 | 0 | let result: nom::IResult<_, _> = nom::combinator::all_consuming( |
307 | 0 | nom::combinator::complete(decode_justifications), |
308 | 0 | )(justifications); |
309 | 0 | match result { |
310 | 0 | Ok((_, out)) => Some(out), |
311 | | Err(nom::Err::Error(_) | nom::Err::Failure(_)) => { |
312 | 0 | return Err(DecodeBlockResponseError::InvalidJustifications) |
313 | | } |
314 | 0 | Err(_) => unreachable!(), |
315 | | } |
316 | | } else { |
317 | 0 | None |
318 | | }, |
319 | | }); |
320 | | } |
321 | | |
322 | 0 | Ok(blocks_out) |
323 | 1 | } _RNvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_block_response Line | Count | Source | 275 | 1 | pub fn decode_block_response( | 276 | 1 | response_bytes: &[u8], | 277 | 1 | ) -> Result<Vec<BlockData>, DecodeBlockResponseError> { | 278 | 1 | let mut parser = nom::combinator::all_consuming::<_, _, nom::error::Error<&[u8]>, _>( | 279 | 1 | nom::combinator::complete(protobuf::message_decode! { | 280 | | #[repeated(max = 32768)] blocks = 1 => protobuf::message_tag_decode(protobuf::message_decode!{ | 281 | | #[required] hash = 1 => protobuf::bytes_tag_decode, | 282 | | #[optional] header = 2 => protobuf::bytes_tag_decode, | 283 | | #[repeated(max = usize::MAX)] body = 3 => protobuf::bytes_tag_decode, | 284 | | #[optional] justifications = 8 => protobuf::bytes_tag_decode, | 285 | | }), | 286 | 1 | }), | 287 | 1 | ); | 288 | | | 289 | 1 | let blocks0 = match nom::Finish::finish(parser(response_bytes)) { | 290 | 0 | Ok((_, out)) => out.blocks, | 291 | 1 | Err(_) => return Err(DecodeBlockResponseError::ProtobufDecode), | 292 | | }; | 293 | | | 294 | 0 | let mut blocks_out = Vec::with_capacity(blocks.len()); | 295 | 0 | for block in blocks { | 296 | 0 | if block.hash.len() != 32 { | 297 | 0 | return Err(DecodeBlockResponseError::InvalidHashLength); | 298 | 0 | } | 299 | 0 |
| 300 | 0 | blocks_out.push(BlockData { | 301 | 0 | hash: <[u8; 32]>::try_from(block.hash).unwrap(), | 302 | 0 | header: block.header.as_ref().map(|h| h.to_vec()), | 303 | 0 | // TODO: no; we might not have asked for the body | 304 | 0 | body: Some(block.body.into_iter().map(|tx| tx.to_vec()).collect()), | 305 | 0 | justifications: if let Some(justifications) = block.justifications { | 306 | 0 | let result: nom::IResult<_, _> = nom::combinator::all_consuming( | 307 | 0 | nom::combinator::complete(decode_justifications), | 308 | 0 | )(justifications); | 309 | 0 | match result { | 310 | 0 | Ok((_, out)) => Some(out), | 311 | | Err(nom::Err::Error(_) | nom::Err::Failure(_)) => { | 312 | 0 | return Err(DecodeBlockResponseError::InvalidJustifications) | 313 | | } | 314 | 0 | Err(_) => unreachable!(), | 315 | | } | 316 | | } else { | 317 | 0 | None | 318 | | }, | 319 | | }); | 320 | | } | 321 | | | 322 | 0 | Ok(blocks_out) | 323 | 1 | } |
Unexecuted instantiation: _RNvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_block_response |
324 | | |
325 | | /// Block sent in a block response. |
326 | | /// |
327 | | /// > **Note**: Assuming that this response comes from the network, the information in this struct |
328 | | /// > can be erroneous and shouldn't be trusted. |
329 | | #[derive(Debug, Clone, PartialEq, Eq)] |
330 | | pub struct BlockData { |
331 | | /// Block hash. |
332 | | /// |
333 | | /// > **Note**: This should contain the hash of the header, but, like the rest of this |
334 | | /// > structure, this cannot be trusted. |
335 | | pub hash: [u8; 32], |
336 | | |
337 | | /// SCALE-encoded block header, if requested. |
338 | | pub header: Option<Vec<u8>>, |
339 | | |
340 | | /// Block body, if requested. Each item (each `Vec<u8>`) is a SCALE-encoded extrinsic. |
341 | | /// These extrinsics aren't decodable, as their meaning depends on the chain. |
342 | | /// |
343 | | /// > **Note**: Be aware that in many chains an extrinsic is actually a `Vec<u8>`, which |
344 | | /// > means that you will find, at the beginning of each SCALE-encoded extrinsic, |
345 | | /// > a length prefix. Don't get fooled into thinking that this length prefix must |
346 | | /// > be removed. It is part of the opaque format extrinsic format. |
347 | | pub body: Option<Vec<Vec<u8>>>, |
348 | | |
349 | | /// List of justifications, if requested and available. |
350 | | /// |
351 | | /// Each justification is a tuple of a "consensus engine id" and a SCALE-encoded |
352 | | /// justifications. |
353 | | /// |
354 | | /// Will be `None` if and only if not requested. |
355 | | pub justifications: Option<Vec<Justification>>, |
356 | | } |
357 | | |
358 | | /// See [`BlockData::justifications`]. |
359 | | #[derive(Debug, Clone, PartialEq, Eq)] |
360 | | pub struct Justification { |
361 | | /// Short identifier of the consensus engine associated with that justification. |
362 | | pub engine_id: [u8; 4], |
363 | | /// Body of the justification. |
364 | | pub justification: Vec<u8>, |
365 | | } |
366 | | |
367 | | /// Error potentially returned by [`decode_block_request`]. |
368 | 0 | #[derive(Debug, derive_more::Display)] Unexecuted instantiation: _RNvXst_NtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_requestNtB5_23DecodeBlockRequestErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXst_NtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_requestNtB5_23DecodeBlockRequestErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
369 | | pub enum DecodeBlockRequestError { |
370 | | /// Error while decoding the Protobuf encoding. |
371 | | ProtobufDecode, |
372 | | /// Zero blocks requested. |
373 | | ZeroBlocksRequested, |
374 | | /// Value in the direction field is invalid. |
375 | | InvalidDirection, |
376 | | /// Start block field is missing. |
377 | | MissingStartBlock, |
378 | | /// Invalid block number passed. |
379 | | InvalidBlockNumber, |
380 | | /// Block hash length isn't correct. |
381 | | InvalidBlockHashLength, |
382 | | /// Requested fields contains bits that are unknown. |
383 | | UnknownFieldBits, |
384 | | } |
385 | | |
386 | | /// Error potentially returned by [`decode_block_response`]. |
387 | 0 | #[derive(Debug, derive_more::Display)] Unexecuted instantiation: _RNvXsv_NtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_requestNtB5_24DecodeBlockResponseErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXsv_NtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_requestNtB5_24DecodeBlockResponseErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
388 | | pub enum DecodeBlockResponseError { |
389 | | /// Error while decoding the Protobuf encoding. |
390 | | ProtobufDecode, |
391 | | /// Hash length isn't of the correct length. |
392 | | InvalidHashLength, |
393 | | BodyDecodeError, |
394 | | /// List of justifications isn't in a correct format. |
395 | | InvalidJustifications, |
396 | | } |
397 | | |
398 | 0 | fn decode_justifications<'a, E: nom::error::ParseError<&'a [u8]>>( |
399 | 0 | bytes: &'a [u8], |
400 | 0 | ) -> nom::IResult<&'a [u8], Vec<Justification>, E> { |
401 | 0 | nom::combinator::flat_map(crate::util::nom_scale_compact_usize, |num_elems| { |
402 | 0 | nom::multi::many_m_n( |
403 | 0 | num_elems, |
404 | 0 | num_elems, |
405 | 0 | nom::combinator::map( |
406 | 0 | nom::sequence::tuple(( |
407 | 0 | nom::bytes::streaming::take(4u32), |
408 | 0 | crate::util::nom_bytes_decode, |
409 | 0 | )), |
410 | 0 | move |(consensus_engine, justification)| Justification { |
411 | 0 | engine_id: <[u8; 4]>::try_from(consensus_engine).unwrap(), |
412 | 0 | justification: justification.to_owned(), |
413 | 0 | }, Unexecuted instantiation: _RNCNCINvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_justificationsINtNtCs6ga8gEqbpRc_3nom5error5ErrorRShEE00Bc_ Unexecuted instantiation: _RNCNCINvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_justificationsINtNtCs6ga8gEqbpRc_3nom5error5ErrorRShEE00Bc_ |
414 | 0 | ), |
415 | 0 | ) |
416 | 0 | })(bytes) Unexecuted instantiation: _RNCINvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_justificationsINtNtCs6ga8gEqbpRc_3nom5error5ErrorRShEE0Ba_ Unexecuted instantiation: _RNCINvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_justificationsINtNtCs6ga8gEqbpRc_3nom5error5ErrorRShEE0Ba_ |
417 | 0 | } Unexecuted instantiation: _RINvNtNtNtCsN16ciHI6Qf_7smoldot7network5codec13block_request21decode_justificationsINtNtCs6ga8gEqbpRc_3nom5error5ErrorRShEEB8_ Unexecuted instantiation: _RINvNtNtNtCseuYC0Zibziv_7smoldot7network5codec13block_request21decode_justificationsINtNtCs6ga8gEqbpRc_3nom5error5ErrorRShEEB8_ |
418 | | |
419 | | #[cfg(test)] |
420 | | mod tests { |
421 | | #[test] |
422 | 1 | fn regression_2339() { |
423 | 1 | // Regression test for https://github.com/paritytech/smoldot/issues/2339. |
424 | 1 | let _ = super::decode_block_request(4, &[26, 10]); |
425 | 1 | } |
426 | | |
427 | | #[test] |
428 | 1 | fn regression_incomplete_justification() { |
429 | 1 | let _ = super::decode_block_response(&[ |
430 | 1 | 200, 200, 255, 255, 10, 8, 0, 47, 0, 1, 26, 0, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, |
431 | 1 | 88, 88, 88, 88, 88, 88, 1, 10, 1, 255, 2, 0, 0, 1, 255, 2, 10, 0, 36, 1, 8, 105, 105, |
432 | 1 | 105, 105, 105, 105, 97, 105, 105, 88, 1, 0, 0, 88, 88, 88, 88, 88, 88, 10, 175, 10, 0, |
433 | 1 | 105, 1, 10, 1, 255, 2, 0, 0, 10, 4, 66, 0, 66, 38, 88, 88, 18, 0, 88, 26, 0, 8, 5, 0, |
434 | 1 | 0, 0, 0, 0, 0, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 88, 88, 88, 88, 88, 0, 0, |
435 | 1 | 88, 88, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 255, 0, 2, 10, 0, 36, 1, 8, 105, |
436 | 1 | 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 10, 48, |
437 | 1 | 10, 0, 105, 1, 10, 2, 0, 12, 0, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, |
438 | 1 | 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, |
439 | 1 | 88, 0, 88, 88, 36, 10, 1, 255, 2, 10, 0, 36, 1, 8, 0, 1, 26, 0, 88, 88, 88, 88, 88, 88, |
440 | 1 | 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 244, |
441 | 1 | 1, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, |
442 | 1 | 0, 26, 1, 88, 88, 0, 0, 0, 0, 0, 0, 26, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, |
443 | 1 | 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 18, 0, 0, 0, 0, 0, |
444 | 1 | 0, 0, 88, 0, 18, 0, 26, 1, 88, 88, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, |
445 | 1 | 10, 1, 255, 2, 10, 0, 105, 1, 86, 0, 0, 0, 0, 0, 0, 0, 8, 105, 105, 105, 105, 105, 105, |
446 | 1 | 97, 105, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 128, 0, 0, 0, 32, 0, |
447 | 1 | 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, |
448 | 1 | 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, |
449 | 1 | 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 0, 32, 0, 0, 0, 0, 18, 0, 26, 1, |
450 | 1 | 88, 88, 88, 88, 36, 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 0, 8, 0, 47, 0, 1, 0, 0, 88, 88, |
451 | 1 | 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, |
452 | 1 | 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, |
453 | 1 | 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 10, 32, 10, 0, 105, 139, |
454 | 1 | 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, |
455 | 1 | 0, 1, 26, 0, 88, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 18, |
456 | 1 | 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, |
457 | 1 | 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, |
458 | 1 | 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, |
459 | 1 | 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 0, 18, 0, 26, 1, 88, 88, 0, 0, 0, 0, |
460 | 1 | 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 86, 0, 0, 0, 0, |
461 | 1 | 0, 0, 0, 8, 105, 105, 105, 105, 105, 105, 97, 105, 88, 88, 88, 88, 88, 10, 48, 10, 0, |
462 | 1 | 105, 1, 10, 1, 255, 2, 0, 0, 0, 0, 32, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, |
463 | 1 | 10, 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 93, 105, 105, 105, 105, 105, |
464 | 1 | 97, 105, 105, 0, 47, 0, 1, 0, 0, 88, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, |
465 | 1 | 255, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, 10, |
466 | 1 | 0, 105, 1, 8, 105, 105, 105, 105, 97, 105, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, |
467 | 1 | 10, 1, 255, 2, 0, 0, 0, 0, 32, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 88, |
468 | 1 | 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 93, 105, 105, 105, 105, 105, 97, 105, |
469 | 1 | 105, 0, 47, 0, 1, 0, 0, 88, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, |
470 | 1 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, |
471 | 1 | 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, |
472 | 1 | 88, 10, 32, 10, 0, 105, 139, 10, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, |
473 | 1 | 1, 255, 2, 10, 0, 105, 1, 86, 0, 0, 0, 0, 0, 0, 0, 8, 105, 105, 105, 105, 105, 105, 97, |
474 | 1 | 105, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 0, 32, 0, 0, 0, |
475 | 1 | 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, |
476 | 1 | 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, |
477 | 1 | 10, 48, 10, 0, 105, 0, 10, 1, 255, 2, 0, 0, 0, 0, 32, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, |
478 | 1 | 88, 88, 36, 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 0, 8, 0, 47, 0, 1, 0, 0, 88, 88, 88, 88, |
479 | 1 | 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, |
480 | 1 | 88, 88, 88, 88, 36, 142, 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, |
481 | 1 | 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 10, 32, 10, 0, 105, 139, 10, 1, 255, |
482 | 1 | 2, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 0, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, |
483 | 1 | 88, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, 10, 0, 36, 1, 8, 105, 105, 105, 105, |
484 | 1 | 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 2, 0, 0, 0, 0, 32, 88, 36, 10, 1, |
485 | 1 | 255, 255, 255, 251, 2, 10, 0, 105, 1, 86, 0, 0, 0, 0, 0, 0, 0, 8, 105, 105, 105, 105, |
486 | 1 | 105, 105, 97, 105, 88, 88, 88, 88, 0, 0, 0, 0, 32, 0, 0, 0, 0, 18, 5, 26, 1, 88, 88, |
487 | 1 | 88, 88, 36, 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 0, 8, 0, 47, 0, 1, 0, 0, 88, 88, 88, 88, |
488 | 1 | 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 0, 0, 0, 128, 0, 0, 18, 0, 26, |
489 | 1 | 1, 88, 88, 88, 88, 36, 142, 1, 255, 2, 255, 10, 0, 105, 1, 8, 105, 255, 2, 10, 0, 36, |
490 | 1 | 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 2, 0, 0, |
491 | 1 | 0, 0, 1, 255, 2, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, |
492 | 1 | ]); |
493 | 1 | } |
494 | | |
495 | | #[test] |
496 | 1 | fn regression_2833() { |
497 | 1 | // Regression test for https://github.com/paritytech/smoldot/issues/2833. |
498 | 1 | let decoded = super::decode_block_request( |
499 | 1 | 4, |
500 | 1 | &[ |
501 | 1 | 8, 128, 128, 128, 136, 1, 26, 4, 237, 91, 33, 0, 48, 1, 56, 1, |
502 | 1 | ], |
503 | 1 | ) |
504 | 1 | .unwrap(); |
505 | 1 | assert!(matches!0 ( |
506 | 1 | decoded.direction, |
507 | | super::BlocksRequestDirection::Ascending |
508 | | )); |
509 | 1 | } |
510 | | } |