/__w/smoldot/smoldot/repo/lib/src/verify/header_only.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::{ |
19 | | chain::chain_information, |
20 | | header, |
21 | | verify::{aura, babe}, |
22 | | }; |
23 | | |
24 | | use alloc::vec::Vec; |
25 | | use core::{num::NonZeroU64, time::Duration}; |
26 | | |
27 | | /// Configuration for a block verification. |
28 | | pub struct Config<'a> { |
29 | | /// Header of the parent of the block to verify. |
30 | | /// |
31 | | /// The hash of this header must be the one referenced in [`Config::block_header`]. |
32 | | pub parent_block_header: header::HeaderRef<'a>, |
33 | | |
34 | | /// Header of the block to verify. |
35 | | /// |
36 | | /// The `parent_hash` field is the hash of the parent whose storage can be accessed through |
37 | | /// the other fields. |
38 | | pub block_header: header::HeaderRef<'a>, |
39 | | |
40 | | /// Number of bytes used to encode the block number in the header. |
41 | | pub block_number_bytes: usize, |
42 | | |
43 | | /// Configuration items related to the consensus engine. |
44 | | pub consensus: ConfigConsensus<'a>, |
45 | | |
46 | | /// Configuration items related to the finality engine. |
47 | | pub finality: ConfigFinality, |
48 | | |
49 | | /// If `false`, digest items with an unknown consensus engine lead to an error. |
50 | | /// |
51 | | /// Note that blocks must always contain digest items that are relevant to the current |
52 | | /// consensus algorithm. This option controls what happens when blocks contain additional |
53 | | /// digest items that aren't recognized by the implementation. |
54 | | /// |
55 | | /// Passing `true` can lead to blocks being considered as valid when they shouldn't, as these |
56 | | /// additional digest items could have some logic attached to them that restricts which blocks |
57 | | /// are valid and which are not. |
58 | | /// |
59 | | /// However, since a recognized consensus engine must always be present, both `true` and |
60 | | /// `false` guarantee that the number of authorable blocks over the network is bounded. |
61 | | pub allow_unknown_consensus_engines: bool, |
62 | | } |
63 | | |
64 | | /// Extra items of [`Config`] that are dependant on the consensus engine of the chain. |
65 | | pub enum ConfigConsensus<'a> { |
66 | | /// Chain is using the Aura consensus engine. |
67 | | Aura { |
68 | | /// Aura authorities that must validate the block. |
69 | | /// |
70 | | /// This list is either equal to the parent's list, or, if the parent changes the list of |
71 | | /// authorities, equal to that new modified list. |
72 | | current_authorities: header::AuraAuthoritiesIter<'a>, |
73 | | |
74 | | /// Duration of a slot in milliseconds. |
75 | | /// Can be found by calling the `AuraApi_slot_duration` runtime function. |
76 | | slot_duration: NonZeroU64, |
77 | | |
78 | | /// Time elapsed since [the Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (i.e. |
79 | | /// 00:00:00 UTC on 1 January 1970), ignoring leap seconds. |
80 | | now_from_unix_epoch: Duration, |
81 | | }, |
82 | | |
83 | | /// Chain is using the Babe consensus engine. |
84 | | Babe { |
85 | | /// Number of slots per epoch in the Babe configuration. |
86 | | slots_per_epoch: NonZeroU64, |
87 | | |
88 | | /// Epoch the parent block belongs to. Must be `None` if and only if the parent block's |
89 | | /// number is 0, as block #0 doesn't belong to any epoch. |
90 | | parent_block_epoch: Option<chain_information::BabeEpochInformationRef<'a>>, |
91 | | |
92 | | /// Epoch that follows the epoch the parent block belongs to. |
93 | | parent_block_next_epoch: chain_information::BabeEpochInformationRef<'a>, |
94 | | |
95 | | /// Time elapsed since [the Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (i.e. |
96 | | /// 00:00:00 UTC on 1 January 1970), ignoring leap seconds. |
97 | | now_from_unix_epoch: Duration, |
98 | | }, |
99 | | } |
100 | | |
101 | | /// Extra items of [`Config`] that are dependant on the finality engine of the chain. |
102 | | pub enum ConfigFinality { |
103 | | /// Blocks themselves don't contain any information concerning finality. Finality is provided |
104 | | /// by a mechanism that is entirely external to the chain. |
105 | | /// |
106 | | /// > **Note**: This is the mechanism used for parachains. Finality is provided entirely by |
107 | | /// > the relay chain. |
108 | | Outsourced, |
109 | | |
110 | | /// Chain uses the Grandpa finality algorithm. |
111 | | Grandpa, |
112 | | } |
113 | | |
114 | | /// Block successfully verified. |
115 | | pub enum Success { |
116 | | /// Chain is using the Aura consensus engine. |
117 | | Aura { |
118 | | /// `Some` if the list of authorities is modified by this block. Contains the new list of |
119 | | /// authorities. |
120 | | authorities_change: Option<Vec<header::AuraAuthority>>, |
121 | | }, |
122 | | |
123 | | /// Chain is using the Babe consensus engine. |
124 | | Babe { |
125 | | /// Slot number the block belongs to. |
126 | | /// |
127 | | /// > **Note**: This is a simple reminder. The value can also be found in the header of the |
128 | | /// > block. |
129 | | slot_number: u64, |
130 | | |
131 | | /// `true` if the claimed slot is a primary slot. `false` if it is a secondary slot. |
132 | | is_primary_slot: bool, |
133 | | |
134 | | /// If `Some`, the verified block contains an epoch transition describing the new |
135 | | /// "next epoch". When verifying blocks that are children of this one, the value in this |
136 | | /// field must be provided as [`ConfigConsensus::Babe::parent_block_next_epoch`], and the |
137 | | /// value previously in [`ConfigConsensus::Babe::parent_block_next_epoch`] must instead be |
138 | | /// passed as [`ConfigConsensus::Babe::parent_block_epoch`]. |
139 | | epoch_transition_target: Option<chain_information::BabeEpochInformation>, |
140 | | }, |
141 | | } |
142 | | |
143 | | /// Error that can happen during the verification. |
144 | 0 | #[derive(Debug, derive_more::Display)] Unexecuted instantiation: _RNvXs0_NtNtCsN16ciHI6Qf_7smoldot6verify11header_onlyNtB5_5ErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXs0_NtNtCseuYC0Zibziv_7smoldot6verify11header_onlyNtB5_5ErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
145 | | pub enum Error { |
146 | | /// Number of the block to verify isn't equal to the parent block's number plus one. |
147 | | NonSequentialBlockNumber, |
148 | | /// Hash of the parent block doesn't match the hash in the header to verify. |
149 | | BadParentHash, |
150 | | /// Block header contains an unrecognized consensus engine. |
151 | | #[display(fmt = "Block header contains an unrecognized consensus engine: {engine:?}")] |
152 | | UnknownConsensusEngine { engine: [u8; 4] }, |
153 | | /// Block header contains items relevant to multiple consensus engines at the same time. |
154 | | MultipleConsensusEngines, |
155 | | /// Block header contains items that don't match the finality engine of the chain. |
156 | | FinalityEngineMismatch, |
157 | | /// Failed to verify the authenticity of the block with the AURA algorithm. |
158 | | #[display(fmt = "{_0}")] |
159 | | AuraVerification(aura::VerifyError), |
160 | | /// Failed to verify the authenticity of the block with the BABE algorithm. |
161 | | #[display(fmt = "{_0}")] |
162 | | BabeVerification(babe::VerifyError), |
163 | | /// Block schedules a Grandpa authorities change while another change is still in progress. |
164 | | GrandpaChangesOverlap, |
165 | | } |
166 | | |
167 | | impl Error { |
168 | | /// Returns `true` if the error isn't actually about the block being verified but about a |
169 | | /// bad configuration of the chain. |
170 | 0 | pub fn is_invalid_chain_configuration(&self) -> bool { |
171 | 0 | matches!( |
172 | 0 | self, |
173 | | Error::BabeVerification(babe::VerifyError::InvalidChainConfiguration(_)) |
174 | | ) |
175 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot6verify11header_onlyNtB2_5Error30is_invalid_chain_configuration Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot6verify11header_onlyNtB2_5Error30is_invalid_chain_configuration |
176 | | } |
177 | | |
178 | | /// Verifies whether a block is valid. |
179 | 4 | pub fn verify(config: Config) -> Result<Success, Error> { |
180 | 4 | // Check that there is no mismatch in the parent header hash. |
181 | 4 | // Note that the user is expected to pass a parent block that matches the parent indicated by |
182 | 4 | // the header to verify, and not blindly pass an "expected parent". As such, this check is |
183 | 4 | // unnecessary and introduces an overhead. |
184 | 4 | // However this check is performed anyway, as the consequences of a failure here could be |
185 | 4 | // potentially quite high. |
186 | 4 | if config.parent_block_header.hash(config.block_number_bytes) |
187 | 4 | != *config.block_header.parent_hash |
188 | | { |
189 | 0 | return Err(Error::BadParentHash); |
190 | 4 | } |
191 | 4 | |
192 | 4 | // Some basic verification of the block number. This is normally verified by the runtime, but |
193 | 4 | // no runtime call can be performed with only the header. |
194 | 4 | if config |
195 | 4 | .parent_block_header |
196 | 4 | .number |
197 | 4 | .checked_add(1) |
198 | 4 | .map_or(true, |v| v != config.block_header.number) _RNCNvNtNtCsN16ciHI6Qf_7smoldot6verify11header_only6verify0B7_ Line | Count | Source | 198 | 4 | .map_or(true, |v| v != config.block_header.number) |
Unexecuted instantiation: _RNCNvNtNtCseuYC0Zibziv_7smoldot6verify11header_only6verify0B7_ |
199 | | { |
200 | 0 | return Err(Error::NonSequentialBlockNumber); |
201 | 4 | } |
202 | 4 | |
203 | 4 | // Fail verification if there is any digest log item with an unrecognized consensus engine. |
204 | 4 | if !config.allow_unknown_consensus_engines { |
205 | 4 | if let Some(engine0 ) = config |
206 | 4 | .block_header |
207 | 4 | .digest |
208 | 4 | .logs() |
209 | 10 | .find_map(|item| match item { |
210 | 0 | header::DigestItemRef::UnknownConsensus { engine, .. } |
211 | 0 | | header::DigestItemRef::UnknownSeal { engine, .. } |
212 | 0 | | header::DigestItemRef::UnknownPreRuntime { engine, .. } => Some(engine), |
213 | 10 | _ => None, |
214 | 10 | })4 _RNCNvNtNtCsN16ciHI6Qf_7smoldot6verify11header_only6verifys_0B7_ Line | Count | Source | 209 | 10 | .find_map(|item| match item { | 210 | 0 | header::DigestItemRef::UnknownConsensus { engine, .. } | 211 | 0 | | header::DigestItemRef::UnknownSeal { engine, .. } | 212 | 0 | | header::DigestItemRef::UnknownPreRuntime { engine, .. } => Some(engine), | 213 | 10 | _ => None, | 214 | 10 | }) |
Unexecuted instantiation: _RNCNvNtNtCseuYC0Zibziv_7smoldot6verify11header_only6verifys_0B7_ |
215 | | { |
216 | 0 | return Err(Error::UnknownConsensusEngine { engine }); |
217 | 4 | } |
218 | 0 | } |
219 | | |
220 | | // Check whether the log items respect the finality engine. |
221 | 4 | match config.finality { |
222 | 4 | ConfigFinality::Grandpa => {} |
223 | | ConfigFinality::Outsourced => { |
224 | 0 | if config.block_header.digest.has_any_grandpa() { |
225 | 0 | return Err(Error::FinalityEngineMismatch); |
226 | 0 | } |
227 | | } |
228 | | } |
229 | | |
230 | 4 | match config.consensus { |
231 | | ConfigConsensus::Aura { |
232 | 0 | current_authorities, |
233 | 0 | slot_duration, |
234 | 0 | now_from_unix_epoch, |
235 | 0 | } => { |
236 | 0 | if config.block_header.digest.has_any_babe() { |
237 | 0 | return Err(Error::MultipleConsensusEngines); |
238 | 0 | } |
239 | 0 |
|
240 | 0 | let result = aura::verify_header(aura::VerifyConfig { |
241 | 0 | header: config.block_header.clone(), |
242 | 0 | block_number_bytes: config.block_number_bytes, |
243 | 0 | parent_block_header: config.parent_block_header, |
244 | 0 | now_from_unix_epoch, |
245 | 0 | current_authorities, |
246 | 0 | slot_duration, |
247 | 0 | }); |
248 | 0 |
|
249 | 0 | match result { |
250 | 0 | Ok(s) => Ok(Success::Aura { |
251 | 0 | authorities_change: s.authorities_change, |
252 | 0 | }), |
253 | 0 | Err(err) => Err(Error::AuraVerification(err)), |
254 | | } |
255 | | } |
256 | | ConfigConsensus::Babe { |
257 | 4 | parent_block_epoch, |
258 | 4 | parent_block_next_epoch, |
259 | 4 | slots_per_epoch, |
260 | 4 | now_from_unix_epoch, |
261 | 4 | } => { |
262 | 4 | if config.block_header.digest.has_any_aura() { |
263 | 0 | return Err(Error::MultipleConsensusEngines); |
264 | 4 | } |
265 | 4 | |
266 | 4 | let result = babe::verify_header(babe::VerifyConfig { |
267 | 4 | header: config.block_header.clone(), |
268 | 4 | block_number_bytes: config.block_number_bytes, |
269 | 4 | parent_block_header: config.parent_block_header, |
270 | 4 | parent_block_epoch, |
271 | 4 | parent_block_next_epoch, |
272 | 4 | slots_per_epoch, |
273 | 4 | now_from_unix_epoch, |
274 | 4 | }); |
275 | 4 | |
276 | 4 | match result { |
277 | 4 | Ok(s) => Ok(Success::Babe { |
278 | 4 | epoch_transition_target: s.epoch_transition_target, |
279 | 4 | is_primary_slot: s.is_primary_slot, |
280 | 4 | slot_number: s.slot_number, |
281 | 4 | }), |
282 | 0 | Err(err) => Err(Error::BabeVerification(err)), |
283 | | } |
284 | | } |
285 | | } |
286 | 4 | } _RNvNtNtCsN16ciHI6Qf_7smoldot6verify11header_only6verify Line | Count | Source | 179 | 4 | pub fn verify(config: Config) -> Result<Success, Error> { | 180 | 4 | // Check that there is no mismatch in the parent header hash. | 181 | 4 | // Note that the user is expected to pass a parent block that matches the parent indicated by | 182 | 4 | // the header to verify, and not blindly pass an "expected parent". As such, this check is | 183 | 4 | // unnecessary and introduces an overhead. | 184 | 4 | // However this check is performed anyway, as the consequences of a failure here could be | 185 | 4 | // potentially quite high. | 186 | 4 | if config.parent_block_header.hash(config.block_number_bytes) | 187 | 4 | != *config.block_header.parent_hash | 188 | | { | 189 | 0 | return Err(Error::BadParentHash); | 190 | 4 | } | 191 | 4 | | 192 | 4 | // Some basic verification of the block number. This is normally verified by the runtime, but | 193 | 4 | // no runtime call can be performed with only the header. | 194 | 4 | if config | 195 | 4 | .parent_block_header | 196 | 4 | .number | 197 | 4 | .checked_add(1) | 198 | 4 | .map_or(true, |v| v != config.block_header.number) | 199 | | { | 200 | 0 | return Err(Error::NonSequentialBlockNumber); | 201 | 4 | } | 202 | 4 | | 203 | 4 | // Fail verification if there is any digest log item with an unrecognized consensus engine. | 204 | 4 | if !config.allow_unknown_consensus_engines { | 205 | 4 | if let Some(engine0 ) = config | 206 | 4 | .block_header | 207 | 4 | .digest | 208 | 4 | .logs() | 209 | 4 | .find_map(|item| match item { | 210 | | header::DigestItemRef::UnknownConsensus { engine, .. } | 211 | | | header::DigestItemRef::UnknownSeal { engine, .. } | 212 | | | header::DigestItemRef::UnknownPreRuntime { engine, .. } => Some(engine), | 213 | | _ => None, | 214 | 4 | }) | 215 | | { | 216 | 0 | return Err(Error::UnknownConsensusEngine { engine }); | 217 | 4 | } | 218 | 0 | } | 219 | | | 220 | | // Check whether the log items respect the finality engine. | 221 | 4 | match config.finality { | 222 | 4 | ConfigFinality::Grandpa => {} | 223 | | ConfigFinality::Outsourced => { | 224 | 0 | if config.block_header.digest.has_any_grandpa() { | 225 | 0 | return Err(Error::FinalityEngineMismatch); | 226 | 0 | } | 227 | | } | 228 | | } | 229 | | | 230 | 4 | match config.consensus { | 231 | | ConfigConsensus::Aura { | 232 | 0 | current_authorities, | 233 | 0 | slot_duration, | 234 | 0 | now_from_unix_epoch, | 235 | 0 | } => { | 236 | 0 | if config.block_header.digest.has_any_babe() { | 237 | 0 | return Err(Error::MultipleConsensusEngines); | 238 | 0 | } | 239 | 0 |
| 240 | 0 | let result = aura::verify_header(aura::VerifyConfig { | 241 | 0 | header: config.block_header.clone(), | 242 | 0 | block_number_bytes: config.block_number_bytes, | 243 | 0 | parent_block_header: config.parent_block_header, | 244 | 0 | now_from_unix_epoch, | 245 | 0 | current_authorities, | 246 | 0 | slot_duration, | 247 | 0 | }); | 248 | 0 |
| 249 | 0 | match result { | 250 | 0 | Ok(s) => Ok(Success::Aura { | 251 | 0 | authorities_change: s.authorities_change, | 252 | 0 | }), | 253 | 0 | Err(err) => Err(Error::AuraVerification(err)), | 254 | | } | 255 | | } | 256 | | ConfigConsensus::Babe { | 257 | 4 | parent_block_epoch, | 258 | 4 | parent_block_next_epoch, | 259 | 4 | slots_per_epoch, | 260 | 4 | now_from_unix_epoch, | 261 | 4 | } => { | 262 | 4 | if config.block_header.digest.has_any_aura() { | 263 | 0 | return Err(Error::MultipleConsensusEngines); | 264 | 4 | } | 265 | 4 | | 266 | 4 | let result = babe::verify_header(babe::VerifyConfig { | 267 | 4 | header: config.block_header.clone(), | 268 | 4 | block_number_bytes: config.block_number_bytes, | 269 | 4 | parent_block_header: config.parent_block_header, | 270 | 4 | parent_block_epoch, | 271 | 4 | parent_block_next_epoch, | 272 | 4 | slots_per_epoch, | 273 | 4 | now_from_unix_epoch, | 274 | 4 | }); | 275 | 4 | | 276 | 4 | match result { | 277 | 4 | Ok(s) => Ok(Success::Babe { | 278 | 4 | epoch_transition_target: s.epoch_transition_target, | 279 | 4 | is_primary_slot: s.is_primary_slot, | 280 | 4 | slot_number: s.slot_number, | 281 | 4 | }), | 282 | 0 | Err(err) => Err(Error::BabeVerification(err)), | 283 | | } | 284 | | } | 285 | | } | 286 | 4 | } |
Unexecuted instantiation: _RNvNtNtCseuYC0Zibziv_7smoldot6verify11header_only6verify |