Coverage Report

Created: 2025-07-01 09:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/__w/smoldot/smoldot/repo/lib/src/network/codec/grandpa_warp_sync.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
//! The GrandPa warp sync protocol is a request-response protocol.
19
//!
20
//! The request's body consists in a block hash.
21
//!
22
//! The response's body consists in a sequence of so-called *fragments*. Each fragment consists in
23
//! a block header and a GrandPa justification corresponding to this header. The justification
24
//! must be verified.
25
//!
26
//! The fragments only contain blocks higher than the hash of the block passed in the request.
27
//!
28
//! By doing a GrandPa warp sync request, a node is capable of quickly obtaining a proof that a
29
//! certain recent block has been finalized by authorities.
30
//!
31
//! A proof has to be minimum. All the headers in all fragments, except for the last, have to
32
//! contain a change in the set of GrandPa authorities.
33
//!
34
//! The responding node has the possibility to cut proofs that are above a certain threshold. When
35
//! it does so, [`GrandpaWarpSyncResponse::is_finished`] should be set to `false`, so that the
36
//! requester can start additional warp sync requests afterwards.
37
38
use crate::{finality, header};
39
40
use alloc::vec::Vec;
41
42
// TODO: all the constraints explained here should be checked when decoding the message
43
44
/// Response to a GrandPa warp sync request.
45
#[derive(Debug)]
46
pub struct GrandpaWarpSyncResponse<'a> {
47
    /// List of fragments that consist in the proof.
48
    ///
49
    /// The fragments must be ordered by ascending block height.
50
    pub fragments: Vec<GrandpaWarpSyncResponseFragment<'a>>,
51
52
    /// If `true`, the last fragment corresponds to the highest finalized block known to the
53
    /// responder. If `false`, the requested is encouraged to start a follow-up GrandPa warp sync
54
    /// request starting at the last block in the fragments.
55
    pub is_finished: bool,
56
}
57
58
/// Response to a GrandPa warp sync request.
59
#[derive(Debug)]
60
pub struct GrandpaWarpSyncResponseFragment<'a> {
61
    /// Header of a block in the chain.
62
    ///
63
    /// Must always contain a change in the list of authorities, except for the last fragment
64
    /// if [`GrandpaWarpSyncResponse::is_finished`] is `true`.
65
    pub scale_encoded_header: &'a [u8],
66
67
    /// Justification that proves the finality of
68
    /// [`GrandpaWarpSyncResponseFragment::scale_encoded_header`].
69
    pub scale_encoded_justification: &'a [u8],
70
}
71
72
/// Error potentially returned by [`decode_grandpa_warp_sync_response`].
73
#[derive(Debug, derive_more::Display, derive_more::Error)]
74
#[display("Failed to decode response")]
75
pub struct DecodeGrandpaWarpSyncResponseError;
76
77
/// Decodes a SCALE-encoded GrandPa warp sync response.
78
0
pub fn decode_grandpa_warp_sync_response(
79
0
    encoded: &[u8],
80
0
    block_number_bytes: usize,
81
0
) -> Result<GrandpaWarpSyncResponse, DecodeGrandpaWarpSyncResponseError> {
82
0
    nom::Parser::parse(
83
0
        &mut nom::combinator::all_consuming::<_, (&[u8], nom::error::ErrorKind), _>(
84
0
            nom::combinator::map(
85
0
                (
86
0
                    decode_fragments(block_number_bytes),
87
0
                    nom::number::streaming::le_u8,
88
0
                ),
89
                |(fragments, is_finished)| GrandpaWarpSyncResponse {
90
0
                    fragments,
91
0
                    is_finished: is_finished != 0,
92
0
                },
Unexecuted instantiation: _RNCNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync33decode_grandpa_warp_sync_response0B9_
Unexecuted instantiation: _RNCNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync33decode_grandpa_warp_sync_response0B9_
93
            ),
94
        ),
95
0
        encoded,
96
    )
97
0
    .map(|(_, parse_result)| parse_result)
98
0
    .map_err(|_| DecodeGrandpaWarpSyncResponseError)
99
0
}
Unexecuted instantiation: _RNvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync33decode_grandpa_warp_sync_response
Unexecuted instantiation: _RNvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync33decode_grandpa_warp_sync_response
100
101
0
fn decode_fragments<'a, E: nom::error::ParseError<&'a [u8]>>(
102
0
    block_number_bytes: usize,
103
0
) -> impl nom::Parser<&'a [u8], Output = Vec<GrandpaWarpSyncResponseFragment<'a>>, Error = E> {
104
0
    nom::combinator::flat_map(crate::util::nom_scale_compact_usize, move |num_elems| {
105
0
        nom::multi::many_m_n(num_elems, num_elems, decode_fragment(block_number_bytes))
106
0
    })
Unexecuted instantiation: _RNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync16decode_fragmentspE0Ba_
Unexecuted instantiation: _RNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync16decode_fragmentsTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEE0Ba_
107
0
}
Unexecuted instantiation: _RINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync16decode_fragmentspEB8_
Unexecuted instantiation: _RINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync16decode_fragmentsTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEEB8_
108
109
0
fn decode_fragment<'a, E: nom::error::ParseError<&'a [u8]>>(
110
0
    block_number_bytes: usize,
111
0
) -> impl nom::Parser<&'a [u8], Output = GrandpaWarpSyncResponseFragment<'a>, Error = E> {
112
0
    nom::combinator::map(
113
        (
114
0
            nom::combinator::recognize(move |s| {
115
0
                header::decode_partial(s, block_number_bytes)
116
0
                    .map(|(a, b)| (b, a))
Unexecuted instantiation: _RNCNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpE00Bc_
Unexecuted instantiation: _RNCNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEE00Bc_
117
0
                    .map_err(|_| {
118
0
                        nom::Err::Failure(nom::error::make_error(s, nom::error::ErrorKind::Verify))
119
0
                    })
Unexecuted instantiation: _RNCNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpE0s_0Bc_
Unexecuted instantiation: _RNCNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEE0s_0Bc_
120
0
            }),
Unexecuted instantiation: _RNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpE0Ba_
Unexecuted instantiation: _RNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEE0Ba_
121
0
            nom::combinator::recognize(move |s| {
122
0
                finality::decode::decode_partial_grandpa_justification(s, block_number_bytes)
123
0
                    .map(|(a, b)| (b, a))
Unexecuted instantiation: _RNCNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpEs_00Bc_
Unexecuted instantiation: _RNCNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEEs_00Bc_
124
0
                    .map_err(|_| {
125
0
                        nom::Err::Failure(nom::error::make_error(s, nom::error::ErrorKind::Verify))
126
0
                    })
Unexecuted instantiation: _RNCNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpEs_0s_0Bc_
Unexecuted instantiation: _RNCNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEEs_0s_0Bc_
127
0
            }),
Unexecuted instantiation: _RNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpEs_0Ba_
Unexecuted instantiation: _RNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEEs_0Ba_
128
        ),
129
0
        move |(scale_encoded_header, scale_encoded_justification)| {
130
0
            GrandpaWarpSyncResponseFragment {
131
0
                scale_encoded_header,
132
0
                scale_encoded_justification,
133
0
            }
134
0
        },
Unexecuted instantiation: _RNCINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpEs0_0Ba_
Unexecuted instantiation: _RNCINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEEs0_0Ba_
135
    )
136
0
}
Unexecuted instantiation: _RINvNtNtNtCsjlkOsLH0Zfj_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentpEB8_
Unexecuted instantiation: _RINvNtNtNtCsc1ywvx6YAnK_7smoldot7network5codec17grandpa_warp_sync15decode_fragmentTRShNtNtCsfjEU5fc64iO_3nom5error9ErrorKindEEB8_