Skip to main content

smoldot/executor/host/
runtime_version.rs

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//! Wasm runtimes can optionally contain a custom section (as defined in the official WebAssembly
19//! core specification).
20//!
21//! This module is dedicated to finding the custom sections containing the runtime version.
22
23use crate::executor::host;
24
25use alloc::vec::Vec;
26use core::{fmt, ops, str};
27
28pub use super::TrieEntryVersion;
29
30/// Tries to find the custom section containing the runtime version and checks its validity.
31pub fn find_embedded_runtime_version(
32    binary_wasm_module: &[u8],
33) -> Result<Option<CoreVersion>, FindEmbeddedRuntimeVersionError> {
34    let (runtime_version_content, runtime_apis_content) =
35        match find_encoded_embedded_runtime_version_apis(binary_wasm_module) {
36            Ok(EmbeddedRuntimeVersionApis {
37                runtime_version_content: Some(v),
38                runtime_apis_content: Some(a),
39            }) => (v, a),
40            Ok(EmbeddedRuntimeVersionApis {
41                runtime_version_content: None,
42                runtime_apis_content: None,
43            }) => return Ok(None),
44            Ok(_) => return Err(FindEmbeddedRuntimeVersionError::CustomSectionsPresenceMismatch),
45            Err(err) => return Err(FindEmbeddedRuntimeVersionError::FindSections(err)),
46        };
47
48    let mut decoded_runtime_version = match decode(runtime_version_content) {
49        Ok(d) => d,
50        Err(()) => return Err(FindEmbeddedRuntimeVersionError::RuntimeVersionDecode),
51    };
52
53    decoded_runtime_version.apis =
54        match CoreVersionApisRefIter::from_slice_no_length(runtime_apis_content) {
55            Ok(d) => d,
56            Err(err) => return Err(FindEmbeddedRuntimeVersionError::RuntimeApisDecode(err)),
57        };
58
59    Ok(Some(CoreVersion(
60        decoded_runtime_version.scale_encoding_vec(),
61    )))
62}
63
64/// Error returned by [`find_embedded_runtime_version`].
65#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
66pub enum FindEmbeddedRuntimeVersionError {
67    /// Error while finding the custom section.
68    #[display("{_0}")]
69    FindSections(FindEncodedEmbeddedRuntimeVersionApisError),
70    /// Only one of the two desired custom sections is present.
71    CustomSectionsPresenceMismatch,
72    /// Error while decoding the runtime version.
73    RuntimeVersionDecode,
74    /// Error while decoding the runtime APIs.
75    #[display("{_0}")]
76    RuntimeApisDecode(CoreVersionApisFromSliceErr),
77}
78
79/// Returns by [`find_encoded_embedded_runtime_version_apis`].
80#[derive(Debug, Copy, Clone)]
81pub struct EmbeddedRuntimeVersionApis<'a> {
82    /// Content of the `runtime_version` section, if any was found.
83    pub runtime_version_content: Option<&'a [u8]>,
84    /// Content of the `runtime_apis` section, if any was found.
85    pub runtime_apis_content: Option<&'a [u8]>,
86}
87
88/// Tries to find the custom sections containing the runtime version and APIs.
89///
90/// This function does not attempt to decode the content of the custom sections.
91pub fn find_encoded_embedded_runtime_version_apis(
92    binary_wasm_module: &'_ [u8],
93) -> Result<EmbeddedRuntimeVersionApis<'_>, FindEncodedEmbeddedRuntimeVersionApisError> {
94    let mut parser =
95        nom::combinator::all_consuming(nom::combinator::complete(nom::sequence::preceded(
96            (
97                nom::bytes::streaming::tag(&b"\0asm"[..]),
98                nom::bytes::streaming::tag(&[0x1, 0x0, 0x0, 0x0][..]),
99            ),
100            nom::multi::fold_many0(
101                nom::combinator::complete(wasm_section),
102                || (None, None),
103                move |prev_found, in_section| {
104                    match (prev_found, in_section) {
105                        // Not a custom section.
106                        (prev_found, None) => prev_found,
107
108                        // We found a custom section with a name that interests us, but we already
109                        // parsed a custom section with that same name earlier. Continue with the
110                        // value that was parsed earlier.
111                        (
112                            prev_found @ (Some(_), _),
113                            Some(WasmSection {
114                                name: b"runtime_version",
115                                ..
116                            }),
117                        ) => prev_found,
118                        (
119                            prev_found @ (_, Some(_)),
120                            Some(WasmSection {
121                                name: b"runtime_apis",
122                                ..
123                            }),
124                        ) => prev_found,
125
126                        // Found a custom section that interests us, and we didn't find one
127                        // before.
128                        (
129                            (None, prev_rt_apis),
130                            Some(WasmSection {
131                                name: b"runtime_version",
132                                content,
133                            }),
134                        ) => (Some(content), prev_rt_apis),
135                        (
136                            (prev_rt_version, None),
137                            Some(WasmSection {
138                                name: b"runtime_apis",
139                                content,
140                            }),
141                        ) => (prev_rt_version, Some(content)),
142
143                        // Found a custom section with a name that doesn't interest us.
144                        (prev_found, Some(_)) => prev_found,
145                    }
146                },
147            ),
148        )));
149
150    let (runtime_version_content, runtime_apis_content) =
151        match nom::Parser::parse(&mut parser, binary_wasm_module) {
152            Ok((_, content)) => content,
153            Err(_) => return Err(FindEncodedEmbeddedRuntimeVersionApisError::FailedToParse),
154        };
155
156    Ok(EmbeddedRuntimeVersionApis {
157        runtime_version_content,
158        runtime_apis_content,
159    })
160}
161
162/// Error returned by [`find_encoded_embedded_runtime_version_apis`].
163#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
164pub enum FindEncodedEmbeddedRuntimeVersionApisError {
165    /// Failed to parse Wasm binary.
166    FailedToParse,
167}
168
169/// Error while executing `Core_version`.
170#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
171pub enum CoreVersionError {
172    /// Error while decoding the output.
173    Decode,
174    /// Error while starting the execution of the `Core_version` function.
175    #[display("Error while starting the execution of the `Core_version` function: {_0}")]
176    Start(host::StartErr),
177    /// Error during the execution of the `Core_version` function.
178    #[display("Error during the execution of the `Core_version` function: {_0}")]
179    Run(host::Error),
180    /// `Core_version` used a host function that is forbidden in this context.
181    ForbiddenHostFunction,
182}
183
184/// Buffer storing the SCALE-encoded core version.
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct CoreVersion(Vec<u8>);
187
188impl CoreVersion {
189    pub fn from_slice(input: Vec<u8>) -> Result<Self, Vec<u8>> {
190        if decode(&input).is_err() {
191            return Err(input);
192        }
193
194        Ok(CoreVersion(input))
195    }
196
197    pub fn decode(&'_ self) -> CoreVersionRef<'_> {
198        decode(&self.0).unwrap()
199    }
200}
201
202impl AsRef<[u8]> for CoreVersion {
203    fn as_ref(&self) -> &[u8] {
204        &self.0
205    }
206}
207
208/// Runtime specification, once decoded.
209// TODO: explain these fields
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct CoreVersionRef<'a> {
212    pub spec_name: &'a str,
213    pub impl_name: &'a str,
214    pub authoring_version: u32,
215    pub spec_version: u32,
216    pub impl_version: u32,
217
218    /// List of "API"s that the runtime supports.
219    ///
220    /// Each API corresponds to a certain list of runtime entry points.
221    ///
222    /// This field can thus be used in order to determine which runtime entry points are
223    /// available.
224    pub apis: CoreVersionApisRefIter<'a>,
225
226    /// Arbitrary version number corresponding to the transactions encoding version.
227    ///
228    /// Whenever this version number changes, all transactions encoding generated earlier are
229    /// invalidated and should be regenerated.
230    ///
231    /// Older versions of Substrate didn't provide this field. `None` if the field is missing.
232    pub transaction_version: Option<u32>,
233
234    /// Version number of the state trie encoding version.
235    ///
236    /// Version 0 corresponds to a different trie encoding than version 1.
237    ///
238    /// This field has been added to Substrate on 24th December 2021. Older versions of Substrate
239    /// didn't provide this field, in which case it will contain `None`.
240    ///
241    /// `None` should be interpreted the same way as `Some(0)`.
242    pub state_version: Option<TrieEntryVersion>,
243}
244
245impl CoreVersionRef<'_> {
246    /// Returns the SCALE encoding of this data structure.
247    pub fn scale_encoding_vec(&self) -> Vec<u8> {
248        // See https://spec.polkadot.network/#defn-rt-core-version
249
250        let num_apis = self.apis.clone().count();
251
252        // Reserve enough capacity for the various calls to `extend` below.
253        // This is only a reasonable estimate, as we assume 2 bytes for the SCALE-compact-encoded
254        // lengths. In the case of very very very long names, the capacity might be too low.
255        let mut out = Vec::<u8>::with_capacity(
256            2 + self.spec_name.len() + 2 + self.impl_name.len() + 4 + 4 + 4 + num_apis * 12 + 4 + 1,
257        );
258
259        out.extend(crate::util::encode_scale_compact_usize(self.spec_name.len()).as_ref());
260        out.extend(self.spec_name.as_bytes());
261
262        out.extend(crate::util::encode_scale_compact_usize(self.impl_name.len()).as_ref());
263        out.extend(self.impl_name.as_bytes());
264
265        out.extend(self.authoring_version.to_le_bytes());
266        out.extend(self.spec_version.to_le_bytes());
267        out.extend(self.impl_version.to_le_bytes());
268
269        out.extend(crate::util::encode_scale_compact_usize(num_apis).as_ref());
270        for api in self.apis.clone() {
271            out.extend(api.name_hash);
272            out.extend(api.version.to_le_bytes());
273        }
274
275        if let Some(transaction_version) = self.transaction_version {
276            out.extend(transaction_version.to_le_bytes());
277        }
278
279        // TODO: it's not supposed to be allowed to have a CoreVersionRef with a state_version but no transaction_version; the CoreVersionRef struct lets you do that because it was initially designed only for decoding
280        if let Some(state_version) = self.state_version {
281            out.extend(u8::from(state_version).to_le_bytes());
282        }
283
284        out
285    }
286}
287
288/// Iterator to a list of APIs. See [`CoreVersionRef::apis`].
289#[derive(Clone)]
290pub struct CoreVersionApisRefIter<'a> {
291    inner: &'a [u8],
292}
293
294impl<'a> CoreVersionApisRefIter<'a> {
295    /// Decodes a SCALE-encoded list of APIs.
296    ///
297    /// The input slice isn't expected to contain the number of APIs.
298    pub fn from_slice_no_length(input: &'a [u8]) -> Result<Self, CoreVersionApisFromSliceErr> {
299        let result: Result<_, nom::Err<nom::error::Error<&[u8]>>> = nom::Parser::parse(
300            &mut nom::combinator::all_consuming(nom::combinator::complete(nom::combinator::map(
301                nom::combinator::recognize(nom::multi::fold_many0(
302                    nom::combinator::complete(core_version_api),
303                    || {},
304                    |(), _| (),
305                )),
306                |inner| CoreVersionApisRefIter { inner },
307            ))),
308            input,
309        );
310
311        match result {
312            Ok((_, me)) => Ok(me),
313            Err(_) => Err(CoreVersionApisFromSliceErr()),
314        }
315    }
316
317    /// Tries to find within this iterator the given API, and if found returns the version number.
318    ///
319    /// If multiple API versions are found, the highest one is returned.
320    ///
321    /// > **Note**: If you start iterating (for example by calling `next()`) then call this
322    /// >           function, the search will only be performed on the rest of the iterator,
323    /// >           which is typically not what you want. Preferably always call this function
324    /// >           on a fresh iterator.
325    pub fn find_version(&self, api: &str) -> Option<u32> {
326        self.find_versions([api])[0]
327    }
328
329    /// Similar to [`CoreVersionApisRefIter::find_version`], but allows passing multiple API names
330    /// at once. This is more optimized if multiple API names are to be queried.
331    pub fn find_versions<const N: usize>(&self, apis: [&str; N]) -> [Option<u32>; N] {
332        let hashed = core::array::from_fn::<_, N, _>(|n| hash_api_name(apis[n]));
333        let mut out = [None; N];
334
335        for api in self.clone() {
336            for (n, expected) in hashed.iter().enumerate() {
337                if *expected == api.name_hash {
338                    match out[n] {
339                        Some(ref mut v) if *v < api.version => *v = api.version,
340                        Some(_) => {}
341                        ref mut v @ None => *v = Some(api.version),
342                    }
343                }
344            }
345        }
346
347        out
348    }
349
350    /// Returns `true` if this iterator contains the API with the given name and its version is in
351    /// the provided range.
352    ///
353    /// > **Note**: If you start iterating (for example by calling `next()`) then call this
354    /// >           function, the search will only be performed on the rest of the iterator,
355    /// >           which is typically not what you want. Preferably always call this function
356    /// >           on a fresh iterator.
357    pub fn contains(&self, api_name: &str, version_number: impl ops::RangeBounds<u32>) -> bool {
358        self.contains_hashed(&hash_api_name(api_name), version_number)
359    }
360
361    /// Similar to [`CoreVersionApisRefIter::contains`], but allows passing the hash of the
362    /// API name instead of its unhashed version.
363    pub fn contains_hashed(
364        &self,
365        api_name_hash: &[u8; 8],
366        version_number: impl ops::RangeBounds<u32>,
367    ) -> bool {
368        self.clone()
369            .any(|api| api.name_hash == *api_name_hash && version_number.contains(&api.version))
370    }
371}
372
373impl Iterator for CoreVersionApisRefIter<'_> {
374    type Item = CoreVersionApi;
375
376    fn next(&mut self) -> Option<Self::Item> {
377        if self.inner.is_empty() {
378            return None;
379        }
380
381        match core_version_api::<nom::error::Error<&[u8]>>(self.inner) {
382            Ok((rest, item)) => {
383                self.inner = rest;
384                Some(item)
385            }
386
387            // The content is always checked to be valid before creating a
388            // `CoreVersionApisRefIter`.
389            Err(_) => unreachable!(),
390        }
391    }
392}
393
394impl PartialEq for CoreVersionApisRefIter<'_> {
395    fn eq(&self, other: &Self) -> bool {
396        let mut a = self.clone();
397        let mut b = other.clone();
398        loop {
399            match (a.next(), b.next()) {
400                (Some(a), Some(b)) if a == b => {}
401                (None, None) => return true,
402                _ => return false,
403            }
404        }
405    }
406}
407
408impl Eq for CoreVersionApisRefIter<'_> {}
409
410impl fmt::Debug for CoreVersionApisRefIter<'_> {
411    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
412        f.debug_list().entries(self.clone()).finish()
413    }
414}
415
416/// Error potentially returned by [`CoreVersionApisRefIter::from_slice_no_length`].
417#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
418#[display("Error decoding core version APIs")]
419pub struct CoreVersionApisFromSliceErr();
420
421/// Hashes the name of an API in order to be able to compare it to [`CoreVersionApi::name_hash`].
422pub fn hash_api_name(api_name: &str) -> [u8; 8] {
423    let result = blake2_rfc::blake2b::blake2b(8, &[], api_name.as_bytes());
424    result.as_bytes().try_into().unwrap()
425}
426
427/// One API that the runtime supports.
428#[derive(Debug, Clone, PartialEq, Eq, Hash)]
429pub struct CoreVersionApi {
430    /// BLAKE2 hash of length 8 of the name of the API.
431    ///
432    /// > **Note**: Available APIs can be found by searching for `decl_runtime_apis!` in the
433    /// >           Substrate code base. The value stored in this field is the BLAKE2 hash of
434    /// >           length 8 of the trait name declared within `decl_runtime_apis!`.
435    pub name_hash: [u8; 8],
436
437    /// Version of the module. Typical values are `1`, `2`, `3`, ...
438    pub version: u32,
439}
440
441fn decode(scale_encoded: &'_ [u8]) -> Result<CoreVersionRef<'_>, ()> {
442    // See https://spec.polkadot.network/#defn-rt-core-version
443    let result: nom::IResult<_, _> = nom::Parser::parse(
444        &mut nom::combinator::all_consuming(nom::combinator::complete(nom::combinator::map(
445            (
446                crate::util::nom_string_decode,
447                crate::util::nom_string_decode,
448                nom::number::streaming::le_u32,
449                nom::number::streaming::le_u32,
450                nom::number::streaming::le_u32,
451                core_version_apis,
452                nom::branch::alt((
453                    nom::combinator::complete(nom::combinator::map(
454                        nom::number::streaming::le_u32,
455                        Some,
456                    )),
457                    nom::combinator::map(nom::combinator::eof, |_| None),
458                )),
459                nom::branch::alt((
460                    nom::combinator::complete(nom::combinator::map(
461                        nom::bytes::streaming::tag(&[0][..]),
462                        |_| Some(TrieEntryVersion::V0),
463                    )),
464                    nom::combinator::complete(nom::combinator::map(
465                        nom::bytes::streaming::tag(&[1][..]),
466                        |_| Some(TrieEntryVersion::V1),
467                    )),
468                    nom::combinator::map(nom::combinator::eof, |_| None),
469                )),
470            ),
471            |(
472                spec_name,
473                impl_name,
474                authoring_version,
475                spec_version,
476                impl_version,
477                apis,
478                transaction_version,
479                state_version,
480            )| CoreVersionRef {
481                spec_name,
482                impl_name,
483                authoring_version,
484                spec_version,
485                impl_version,
486                apis,
487                transaction_version,
488                state_version,
489            },
490        ))),
491        scale_encoded,
492    );
493
494    match result {
495        Ok((_, out)) => Ok(out),
496        Err(nom::Err::Error(_) | nom::Err::Failure(_)) => Err(()),
497        Err(_) => unreachable!(),
498    }
499}
500
501fn core_version_apis<'a, E: nom::error::ParseError<&'a [u8]>>(
502    bytes: &'a [u8],
503) -> nom::IResult<&'a [u8], CoreVersionApisRefIter<'a>, E> {
504    nom::Parser::parse(
505        &mut nom::combinator::map(
506            nom::combinator::flat_map(crate::util::nom_scale_compact_usize, |num_elems| {
507                nom::combinator::recognize(nom::multi::fold_many_m_n(
508                    num_elems,
509                    num_elems,
510                    core_version_api,
511                    || {},
512                    |(), _| (),
513                ))
514            }),
515            |inner| CoreVersionApisRefIter { inner },
516        ),
517        bytes,
518    )
519}
520
521fn core_version_api<'a, E: nom::error::ParseError<&'a [u8]>>(
522    bytes: &'a [u8],
523) -> nom::IResult<&'a [u8], CoreVersionApi, E> {
524    nom::Parser::parse(
525        &mut nom::combinator::map(
526            (
527                nom::bytes::streaming::take(8u32),
528                nom::number::streaming::le_u32,
529            ),
530            move |(name, version)| CoreVersionApi {
531                name_hash: <[u8; 8]>::try_from(name).unwrap(),
532                version,
533            },
534        ),
535        bytes,
536    )
537}
538
539struct WasmSection<'a> {
540    name: &'a [u8],
541    content: &'a [u8],
542}
543
544/// Parses a Wasm section. If it is a custom section, returns its name and content.
545fn wasm_section(bytes: &'_ [u8]) -> nom::IResult<&'_ [u8], Option<WasmSection<'_>>> {
546    nom::Parser::parse(
547        &mut nom::branch::alt((
548            nom::combinator::map(
549                nom::combinator::map_parser(
550                    nom::sequence::preceded(
551                        nom::bytes::streaming::tag(&[0][..]),
552                        nom::multi::length_data(nom::combinator::map_opt(
553                            crate::util::leb128::nom_leb128_u64,
554                            |n| u32::try_from(n).ok(),
555                        )),
556                    ),
557                    (
558                        nom::multi::length_data(nom::combinator::map_opt(
559                            crate::util::leb128::nom_leb128_u64,
560                            |n| u32::try_from(n).ok(),
561                        )),
562                        nom::combinator::rest,
563                    ),
564                ),
565                |(name, content)| Some(WasmSection { name, content }),
566            ),
567            nom::combinator::map(
568                (
569                    nom::number::streaming::u8,
570                    nom::multi::length_data(nom::combinator::map_opt(
571                        crate::util::leb128::nom_leb128_u64,
572                        |n| u32::try_from(n).ok(),
573                    )),
574                ),
575                |_| None,
576            ),
577        )),
578        bytes,
579    )
580}