Coverage Report

Created: 2024-05-16 12:16

/__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