/__w/smoldot/smoldot/repo/lib/src/network/codec/block_request.rs
Line | Count | Source |
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::NonZero; |
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: NonZero<u32>, |
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 | | // 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 | | ) |
110 | 0 | .chain( |
111 | 0 | protobuf::enum_tag_encode( |
112 | | 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 | | ) |
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 | | ) |
128 | | // The `support_multiple_justifications` flag indicates that we support responses |
129 | | // containing multiple justifications. This flag is simply a way to maintain backwards |
130 | | // compatibility in the protocol. |
131 | 0 | .chain(protobuf::bool_tag_encode(7, true).map(either::Right)) |
132 | 0 | } Unexecuted instantiation: _RNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request19build_block_request Unexecuted instantiation: _RNvNtNtNtCsc1ywvx6YAnK_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 | | #[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 | | }), |
148 | | ); |
149 | | |
150 | 2 | let decoded1 = match nom::Finish::finish(nom::Parser::parse(&mut 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 | | |
166 | | // The exact format is the SCALE encoding of a block number. |
167 | | // The block number can have a varying number of bytes, and it is therefore |
168 | | // not really possible to know how many bytes to expect here. |
169 | | // Because the SCALE encoding of a number is the number in little endian format, |
170 | | // we decode the bytes in little endian format in a way that works no matter the |
171 | | // 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 | NonZero::<u32>::new(decoded.max_blocks.unwrap_or(u32::MAX)) |
194 | 1 | .unwrap_or(NonZero::<u32>::new(u32::MAX).unwrap()) |
195 | | }, |
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 | } _RNvNtNtNtCsjlkOsLH0Zfj_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 | | }), | 148 | | ); | 149 | | | 150 | 2 | let decoded1 = match nom::Finish::finish(nom::Parser::parse(&mut 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 | | | 166 | | // The exact format is the SCALE encoding of a block number. | 167 | | // The block number can have a varying number of bytes, and it is therefore | 168 | | // not really possible to know how many bytes to expect here. | 169 | | // Because the SCALE encoding of a number is the number in little endian format, | 170 | | // we decode the bytes in little endian format in a way that works no matter the | 171 | | // 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 | NonZero::<u32>::new(decoded.max_blocks.unwrap_or(u32::MAX)) | 194 | 1 | .unwrap_or(NonZero::<u32>::new(u32::MAX).unwrap()) | 195 | | }, | 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: _RNvNtNtNtCsc1ywvx6YAnK_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 | | // Note that this function assumes that `support_multiple_justifications` was true in the |
218 | | // request. We intentionally don't support old versions where it was false. |
219 | | |
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: _RNCNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request20build_block_response00Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response00CsfFWJyR6nd6r_17smoldot_full_node Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response00Bb_ |
227 | | ); |
228 | 0 | j.extend_from_slice( |
229 | 0 | crate::util::encode_scale_compact_usize(justifications.len()).as_ref(), |
230 | | ); |
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: _RNCNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request20build_block_response0s_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s_0CsfFWJyR6nd6r_17smoldot_full_node Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s_0Bb_ |
251 | 0 | .map(either::Right), |
252 | | ) |
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: _RNCNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request20build_block_response0s0_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s0_0CsfFWJyR6nd6r_17smoldot_full_node Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s0_0Bb_ |
259 | 0 | .flat_map(|tx| protobuf::bytes_tag_encode(3, tx)) Unexecuted instantiation: _RNCNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request20build_block_response0s1_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s1_0CsfFWJyR6nd6r_17smoldot_full_node Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s1_0Bb_ |
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: _RNCNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request20build_block_response0s2_0Bb_ Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s2_0CsfFWJyR6nd6r_17smoldot_full_node Unexecuted instantiation: _RNCNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0s2_0Bb_ |
265 | 0 | .map(either::Right), |
266 | | ) |
267 | 0 | .map(either::Right), |
268 | | ) |
269 | | }) |
270 | 0 | }) Unexecuted instantiation: _RNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request20build_block_response0B9_ Unexecuted instantiation: _RNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0CsfFWJyR6nd6r_17smoldot_full_node Unexecuted instantiation: _RNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request20build_block_response0B9_ |
271 | 0 | } Unexecuted instantiation: _RNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request20build_block_response Unexecuted instantiation: _RNvNtNtNtCsc1ywvx6YAnK_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 | | #[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 | | }), |
287 | | ); |
288 | | |
289 | 1 | let blocks0 = match nom::Finish::finish(nom::Parser::parse(&mut 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 | | |
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: _RNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request21decode_block_response0B9_ Unexecuted instantiation: _RNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request21decode_block_response0B9_ |
303 | | // 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: _RNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request21decode_block_responses_0B9_ Unexecuted instantiation: _RNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request21decode_block_responses_0B9_ |
305 | 0 | justifications: if let Some(justifications) = block.justifications { |
306 | 0 | let result: nom::IResult<_, _> = nom::Parser::parse( |
307 | 0 | &mut nom::combinator::all_consuming(nom::combinator::complete( |
308 | 0 | decode_justifications, |
309 | 0 | )), |
310 | 0 | justifications, |
311 | | ); |
312 | 0 | match result { |
313 | 0 | Ok((_, out)) => Some(out), |
314 | | Err(nom::Err::Error(_) | nom::Err::Failure(_)) => { |
315 | 0 | return Err(DecodeBlockResponseError::InvalidJustifications); |
316 | | } |
317 | 0 | Err(_) => unreachable!(), |
318 | | } |
319 | | } else { |
320 | 0 | None |
321 | | }, |
322 | | }); |
323 | | } |
324 | | |
325 | 0 | Ok(blocks_out) |
326 | 1 | } _RNvNtNtNtCsjlkOsLH0Zfj_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 | | }), | 287 | | ); | 288 | | | 289 | 1 | let blocks0 = match nom::Finish::finish(nom::Parser::parse(&mut 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 | | | 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 | | // 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::Parser::parse( | 307 | 0 | &mut nom::combinator::all_consuming(nom::combinator::complete( | 308 | 0 | decode_justifications, | 309 | 0 | )), | 310 | 0 | justifications, | 311 | | ); | 312 | 0 | match result { | 313 | 0 | Ok((_, out)) => Some(out), | 314 | | Err(nom::Err::Error(_) | nom::Err::Failure(_)) => { | 315 | 0 | return Err(DecodeBlockResponseError::InvalidJustifications); | 316 | | } | 317 | 0 | Err(_) => unreachable!(), | 318 | | } | 319 | | } else { | 320 | 0 | None | 321 | | }, | 322 | | }); | 323 | | } | 324 | | | 325 | 0 | Ok(blocks_out) | 326 | 1 | } |
Unexecuted instantiation: _RNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request21decode_block_response |
327 | | |
328 | | /// Block sent in a block response. |
329 | | /// |
330 | | /// > **Note**: Assuming that this response comes from the network, the information in this struct |
331 | | /// > can be erroneous and shouldn't be trusted. |
332 | | #[derive(Debug, Clone, PartialEq, Eq)] |
333 | | pub struct BlockData { |
334 | | /// Block hash. |
335 | | /// |
336 | | /// > **Note**: This should contain the hash of the header, but, like the rest of this |
337 | | /// > structure, this cannot be trusted. |
338 | | pub hash: [u8; 32], |
339 | | |
340 | | /// SCALE-encoded block header, if requested. |
341 | | pub header: Option<Vec<u8>>, |
342 | | |
343 | | /// Block body, if requested. Each item (each `Vec<u8>`) is a SCALE-encoded extrinsic. |
344 | | /// These extrinsics aren't decodable, as their meaning depends on the chain. |
345 | | /// |
346 | | /// > **Note**: Be aware that in many chains an extrinsic is actually a `Vec<u8>`, which |
347 | | /// > means that you will find, at the beginning of each SCALE-encoded extrinsic, |
348 | | /// > a length prefix. Don't get fooled into thinking that this length prefix must |
349 | | /// > be removed. It is part of the opaque format extrinsic format. |
350 | | pub body: Option<Vec<Vec<u8>>>, |
351 | | |
352 | | /// List of justifications, if requested and available. |
353 | | /// |
354 | | /// Each justification is a tuple of a "consensus engine id" and a SCALE-encoded |
355 | | /// justifications. |
356 | | /// |
357 | | /// Will be `None` if and only if not requested. |
358 | | pub justifications: Option<Vec<Justification>>, |
359 | | } |
360 | | |
361 | | /// See [`BlockData::justifications`]. |
362 | | #[derive(Debug, Clone, PartialEq, Eq)] |
363 | | pub struct Justification { |
364 | | /// Short identifier of the consensus engine associated with that justification. |
365 | | pub engine_id: [u8; 4], |
366 | | /// Body of the justification. |
367 | | pub justification: Vec<u8>, |
368 | | } |
369 | | |
370 | | /// Error potentially returned by [`decode_block_request`]. |
371 | | #[derive(Debug, derive_more::Display, derive_more::Error)] |
372 | | pub enum DecodeBlockRequestError { |
373 | | /// Error while decoding the Protobuf encoding. |
374 | | ProtobufDecode, |
375 | | /// Zero blocks requested. |
376 | | ZeroBlocksRequested, |
377 | | /// Value in the direction field is invalid. |
378 | | InvalidDirection, |
379 | | /// Start block field is missing. |
380 | | MissingStartBlock, |
381 | | /// Invalid block number passed. |
382 | | InvalidBlockNumber, |
383 | | /// Block hash length isn't correct. |
384 | | InvalidBlockHashLength, |
385 | | /// Requested fields contains bits that are unknown. |
386 | | UnknownFieldBits, |
387 | | } |
388 | | |
389 | | /// Error potentially returned by [`decode_block_response`]. |
390 | | #[derive(Debug, derive_more::Display, derive_more::Error)] |
391 | | pub enum DecodeBlockResponseError { |
392 | | /// Error while decoding the Protobuf encoding. |
393 | | ProtobufDecode, |
394 | | /// Hash length isn't of the correct length. |
395 | | InvalidHashLength, |
396 | | BodyDecodeError, |
397 | | /// List of justifications isn't in a correct format. |
398 | | InvalidJustifications, |
399 | | } |
400 | | |
401 | 0 | fn decode_justifications<'a, E: nom::error::ParseError<&'a [u8]>>( |
402 | 0 | bytes: &'a [u8], |
403 | 0 | ) -> nom::IResult<&'a [u8], Vec<Justification>, E> { |
404 | 0 | nom::Parser::parse( |
405 | 0 | &mut nom::combinator::flat_map(crate::util::nom_scale_compact_usize, |num_elems| { |
406 | 0 | nom::multi::many_m_n( |
407 | 0 | num_elems, |
408 | 0 | num_elems, |
409 | 0 | nom::combinator::map( |
410 | 0 | ( |
411 | 0 | nom::bytes::streaming::take(4u32), |
412 | 0 | crate::util::nom_bytes_decode, |
413 | 0 | ), |
414 | | move |(consensus_engine, justification)| Justification { |
415 | 0 | engine_id: <[u8; 4]>::try_from(consensus_engine).unwrap(), |
416 | 0 | justification: justification.to_owned(), |
417 | 0 | }, Unexecuted instantiation: _RNCNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request21decode_justificationsINtNtCsfjEU5fc64iO_3nom5error5ErrorRShEE00Bc_ Unexecuted instantiation: _RNCNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request21decode_justificationsINtNtCsfjEU5fc64iO_3nom5error5ErrorRShEE00Bc_ |
418 | | ), |
419 | | ) |
420 | 0 | }), Unexecuted instantiation: _RNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request21decode_justificationsINtNtCsfjEU5fc64iO_3nom5error5ErrorRShEE0Ba_ Unexecuted instantiation: _RNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request21decode_justificationsINtNtCsfjEU5fc64iO_3nom5error5ErrorRShEE0Ba_ |
421 | 0 | bytes, |
422 | | ) |
423 | 0 | } Unexecuted instantiation: _RINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec13block_request21decode_justificationsINtNtCsfjEU5fc64iO_3nom5error5ErrorRShEEB8_ Unexecuted instantiation: _RINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec13block_request21decode_justificationsINtNtCsfjEU5fc64iO_3nom5error5ErrorRShEEB8_ |
424 | | |
425 | | #[cfg(test)] |
426 | | mod tests { |
427 | | #[test] |
428 | 1 | fn regression_2339() { |
429 | | // Regression test for https://github.com/paritytech/smoldot/issues/2339. |
430 | 1 | let _ = super::decode_block_request(4, &[26, 10]); |
431 | 1 | } |
432 | | |
433 | | #[test] |
434 | 1 | fn regression_incomplete_justification() { |
435 | 1 | let _ = super::decode_block_response(&[ |
436 | 1 | 200, 200, 255, 255, 10, 8, 0, 47, 0, 1, 26, 0, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, |
437 | 1 | 88, 88, 88, 88, 88, 88, 1, 10, 1, 255, 2, 0, 0, 1, 255, 2, 10, 0, 36, 1, 8, 105, 105, |
438 | 1 | 105, 105, 105, 105, 97, 105, 105, 88, 1, 0, 0, 88, 88, 88, 88, 88, 88, 10, 175, 10, 0, |
439 | 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, |
440 | 1 | 0, 0, 0, 0, 0, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 88, 88, 88, 88, 88, 0, 0, |
441 | 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, |
442 | 1 | 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 10, 48, |
443 | 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, |
444 | 1 | 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, |
445 | 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, |
446 | 1 | 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 244, |
447 | 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, |
448 | 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, |
449 | 1 | 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 18, 0, 0, 0, 0, 0, |
450 | 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, |
451 | 1 | 10, 1, 255, 2, 10, 0, 105, 1, 86, 0, 0, 0, 0, 0, 0, 0, 8, 105, 105, 105, 105, 105, 105, |
452 | 1 | 97, 105, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, 128, 0, 0, 0, 32, 0, |
453 | 1 | 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, |
454 | 1 | 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, |
455 | 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, |
456 | 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, |
457 | 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, |
458 | 1 | 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, |
459 | 1 | 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 10, 32, 10, 0, 105, 139, |
460 | 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, |
461 | 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, |
462 | 1 | 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, |
463 | 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, |
464 | 1 | 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, |
465 | 1 | 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 0, 18, 0, 26, 1, 88, 88, 0, 0, 0, 0, |
466 | 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, |
467 | 1 | 0, 0, 0, 8, 105, 105, 105, 105, 105, 105, 97, 105, 88, 88, 88, 88, 88, 10, 48, 10, 0, |
468 | 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, |
469 | 1 | 10, 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 93, 105, 105, 105, 105, 105, |
470 | 1 | 97, 105, 105, 0, 47, 0, 1, 0, 0, 88, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, |
471 | 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, |
472 | 1 | 0, 105, 1, 8, 105, 105, 105, 105, 97, 105, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, |
473 | 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, |
474 | 1 | 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, 8, 105, 93, 105, 105, 105, 105, 105, 97, 105, |
475 | 1 | 105, 0, 47, 0, 1, 0, 0, 88, 88, 88, 88, 88, 88, 10, 48, 10, 0, 105, 1, 10, 1, 255, 2, |
476 | 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, |
477 | 1 | 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, |
478 | 1 | 88, 10, 32, 10, 0, 105, 139, 10, 0, 0, 0, 0, 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, |
479 | 1 | 1, 255, 2, 10, 0, 105, 1, 86, 0, 0, 0, 0, 0, 0, 0, 8, 105, 105, 105, 105, 105, 105, 97, |
480 | 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, |
481 | 1 | 0, 18, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 88, 88, 36, 10, 1, 255, 2, 10, 0, 105, 1, |
482 | 1 | 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, |
483 | 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, |
484 | 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, |
485 | 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, |
486 | 1 | 88, 88, 88, 88, 36, 142, 1, 255, 2, 10, 0, 105, 1, 8, 105, 105, 105, 105, 105, 105, 97, |
487 | 1 | 105, 105, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 10, 32, 10, 0, 105, 139, 10, 1, 255, |
488 | 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, |
489 | 1 | 88, 0, 26, 1, 88, 88, 88, 88, 36, 10, 1, 255, 2, 10, 0, 36, 1, 8, 105, 105, 105, 105, |
490 | 1 | 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 2, 0, 0, 0, 0, 32, 88, 36, 10, 1, |
491 | 1 | 255, 255, 255, 251, 2, 10, 0, 105, 1, 86, 0, 0, 0, 0, 0, 0, 0, 8, 105, 105, 105, 105, |
492 | 1 | 105, 105, 97, 105, 88, 88, 88, 88, 0, 0, 0, 0, 32, 0, 0, 0, 0, 18, 5, 26, 1, 88, 88, |
493 | 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, |
494 | 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, |
495 | 1 | 1, 88, 88, 88, 88, 36, 142, 1, 255, 2, 255, 10, 0, 105, 1, 8, 105, 255, 2, 10, 0, 36, |
496 | 1 | 1, 8, 105, 105, 105, 105, 105, 105, 97, 105, 105, 105, 88, 88, 88, 88, 88, 88, 2, 0, 0, |
497 | 1 | 0, 0, 1, 255, 2, 105, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, |
498 | 1 | ]); |
499 | 1 | } |
500 | | |
501 | | #[test] |
502 | 1 | fn regression_2833() { |
503 | | // Regression test for https://github.com/paritytech/smoldot/issues/2833. |
504 | 1 | let decoded = super::decode_block_request( |
505 | | 4, |
506 | 1 | &[ |
507 | 1 | 8, 128, 128, 128, 136, 1, 26, 4, 237, 91, 33, 0, 48, 1, 56, 1, |
508 | 1 | ], |
509 | | ) |
510 | 1 | .unwrap(); |
511 | 1 | assert!(matches!0 ( |
512 | 1 | decoded.direction, |
513 | | super::BlocksRequestDirection::Ascending |
514 | | )); |
515 | 1 | } |
516 | | } |