Coverage Report

Created: 2024-05-16 12:16

/__w/smoldot/smoldot/repo/light-base/src/database.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
//! "Database" encoding and decoding.
19
//!
20
//! The light client is capable of serializing the state of the finalized block, which is called
21
//! a database. It is not really what is commonly called a database, but rather simply a small
22
//! JSON document.
23
//! It can later de-serialize this database.
24
//!
25
//! This database doesn't contain just the state of the finalized block, but also other
26
//! information. See [`DatabaseContent`].
27
//!
28
//! This module provides the function to encode and decode this so-called database.
29
30
use alloc::{
31
    borrow::ToOwned as _,
32
    boxed::Box,
33
    format,
34
    string::{String, ToString as _},
35
    vec::Vec,
36
};
37
use core::cmp;
38
use smoldot::{
39
    chain,
40
    database::finalized_serialize,
41
    libp2p::{multiaddr, PeerId},
42
};
43
44
use crate::{network_service, platform, runtime_service, sync_service};
45
46
pub use smoldot::trie::Nibble;
47
48
/// A decoded database.
49
pub struct DatabaseContent {
50
    /// Hash of the genesis block, as provided to [`encode_database`].
51
    pub genesis_block_hash: [u8; 32],
52
53
    /// Information about the finalized chain.
54
    pub chain_information: Option<chain::chain_information::ValidChainInformation>,
55
56
    /// List of nodes that were known to be part of the peer-to-peer network when the database
57
    /// was encoded.
58
    pub known_nodes: Vec<(PeerId, Vec<multiaddr::Multiaddr>)>,
59
60
    /// Known valid Merkle value and storage value combination for the `:code` key.
61
    ///
62
    /// Does **not** necessarily match the finalized block found in
63
    /// [`DatabaseContent::chain_information`].
64
    pub runtime_code_hint: Option<DatabaseContentRuntimeCodeHint>,
65
}
66
67
/// See [`DatabaseContent::runtime_code_hint`].
68
#[derive(Debug, Clone)]
69
pub struct DatabaseContentRuntimeCodeHint {
70
    /// Storage value of the `:code` trie node corresponding to
71
    /// [`DatabaseContentRuntimeCodeHint::code_merkle_value`].
72
    pub code: Vec<u8>,
73
    /// Merkle value of the `:code` trie node in the storage main trie.
74
    pub code_merkle_value: Vec<u8>,
75
    /// Closest ancestor of the `:code` key except for `:code` itself.
76
    // TODO: this punches a bit through abstraction layers, but it's temporary
77
    pub closest_ancestor_excluding: Vec<Nibble>,
78
}
79
80
/// Serializes the finalized state of the chain, using the given services.
81
///
82
/// The returned string is guaranteed to not exceed `max_size` bytes. A truncated or invalid
83
/// database is intentionally returned if `max_size` is too low to fit all the information.
84
0
pub async fn encode_database<TPlat: platform::PlatformRef>(
85
0
    network_service: &network_service::NetworkServiceChain<TPlat>,
86
0
    sync_service: &sync_service::SyncService<TPlat>,
87
0
    runtime_service: &runtime_service::RuntimeService<TPlat>,
88
0
    genesis_block_hash: &[u8; 32],
89
0
    max_size: usize,
90
0
) -> String {
Unexecuted instantiation: _RINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepEB4_
Unexecuted instantiation: _RINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefEB11_
Unexecuted instantiation: _RINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepEB4_
91
0
    let (code_storage_value, code_merkle_value, code_closest_ancestor_excluding) = runtime_service
92
0
        .finalized_runtime_storage_merkle_values()
93
0
        .await
94
0
        .unwrap_or((None, None, None));
95
96
    // Craft the structure containing all the data that we would like to include.
97
0
    let mut database_draft = SerdeDatabase {
98
0
        genesis_hash: hex::encode(genesis_block_hash),
99
0
        chain: sync_service.serialize_chain_information().await.map(|ci| {
100
0
            let encoded = finalized_serialize::encode_chain(&ci, sync_service.block_number_bytes());
101
0
            serde_json::from_str(&encoded).unwrap()
102
0
        }),
Unexecuted instantiation: _RNCNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE00B8_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE00B15_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE00B8_
103
0
        nodes: network_service
104
0
            .discovered_nodes()
105
0
            .await
106
0
            .map(|(peer_id, addrs)| {
107
0
                (
108
0
                    peer_id.to_base58(),
109
0
                    addrs.map(|a| a.to_string()).collect::<Vec<_>>(),
Unexecuted instantiation: _RNCNCNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE0s_00Ba_
Unexecuted instantiation: _RNCNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE0s_00B17_
Unexecuted instantiation: _RNCNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE0s_00Ba_
110
0
                )
111
0
            })
Unexecuted instantiation: _RNCNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE0s_0B8_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE0s_0B15_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE0s_0B8_
112
0
            .collect(),
113
0
        code_merkle_value: code_merkle_value.map(hex::encode),
114
0
        // While it might seem like a good idea to compress the runtime code, in practice it is
115
0
        // normally already zstd-compressed, and additional compressing shouldn't improve the size.
116
0
        code_storage_value: code_storage_value.map(|data| {
117
0
            base64::Engine::encode(&base64::engine::general_purpose::STANDARD_NO_PAD, data)
118
0
        }),
Unexecuted instantiation: _RNCNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE0s0_0B8_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE0s0_0B15_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE0s0_0B8_
119
0
        code_closest_ancestor_excluding: code_closest_ancestor_excluding.map(|key| {
120
0
            key.iter()
121
0
                .map(|nibble| format!("{:x}", nibble))
Unexecuted instantiation: _RNCNCNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE0s1_00Ba_
Unexecuted instantiation: _RNCNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE0s1_00B17_
Unexecuted instantiation: _RNCNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE0s1_00Ba_
122
0
                .collect::<String>()
123
0
        }),
Unexecuted instantiation: _RNCNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE0s1_0B8_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE0s1_0B15_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE0s1_0B8_
124
    };
125
126
    // Cap the database length to the maximum size.
127
    loop {
128
0
        let serialized = serde_json::to_string(&database_draft).unwrap();
129
0
        if serialized.len() <= max_size {
130
            // Success!
131
0
            return serialized;
132
0
        }
133
0
134
0
        // Scrap the code, as it is the biggest item.
135
0
        if database_draft.code_merkle_value.is_some() || database_draft.code_storage_value.is_some()
136
        {
137
0
            database_draft.code_merkle_value = None;
138
0
            database_draft.code_storage_value = None;
139
0
            continue;
140
0
        }
141
0
142
0
        if database_draft.nodes.is_empty() {
143
            // Can't shrink the database anymore. Return the string `"<too-large>"` which will
144
            // fail to decode but will indicate what is wrong.
145
0
            let dummy_message = "<too-large>";
146
0
            return if dummy_message.len() > max_size {
147
0
                String::new()
148
            } else {
149
0
                dummy_message.to_owned()
150
            };
151
0
        }
152
0
153
0
        // Try to reduce the size of the database.
154
0
155
0
        // Remove half of the nodes.
156
0
        // Which nodes are removed doesn't really matter.
157
0
        let mut nodes_to_remove = cmp::max(1, database_draft.nodes.len() / 2);
158
0
        database_draft.nodes.retain(|_, _| {
159
0
            if nodes_to_remove >= 1 {
160
0
                nodes_to_remove -= 1;
161
0
                false
162
            } else {
163
0
                true
164
            }
165
0
        });
Unexecuted instantiation: _RNCNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE0s2_0B8_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE0s2_0B15_
Unexecuted instantiation: _RNCNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE0s2_0B8_
166
    }
167
0
}
Unexecuted instantiation: _RNCINvNtCsiGub1lfKphe_13smoldot_light8database15encode_databasepE0B6_
Unexecuted instantiation: _RNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databaseNtNtCsDDUKWWCHAU_18smoldot_light_wasm8platform11PlatformRefE0B13_
Unexecuted instantiation: _RNCINvNtCsih6EgvAwZF2_13smoldot_light8database15encode_databasepE0B6_
168
169
/// Tries to decode the given database.
170
///
171
/// An error is returned if the data is in an invalid format.
172
///
173
/// Must be passed the number of bytes used to encode the number of a block for the given chain.
174
0
pub fn decode_database(encoded: &str, block_number_bytes: usize) -> Result<DatabaseContent, ()> {
175
0
    let decoded: SerdeDatabase = serde_json::from_str(encoded).map_err(|_| ())?;
Unexecuted instantiation: _RNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_database0B5_
Unexecuted instantiation: _RNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_database0B5_
176
177
0
    let genesis_block_hash = if decoded.genesis_hash.len() == 64 {
178
0
        <[u8; 32]>::try_from(hex::decode(&decoded.genesis_hash).map_err(|_| ())?).unwrap()
Unexecuted instantiation: _RNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_databases_0B5_
Unexecuted instantiation: _RNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_databases_0B5_
179
    } else {
180
0
        return Err(());
181
    };
182
183
0
    let chain_information = match &decoded.chain {
184
0
        Some(chain) => Some(
185
0
            finalized_serialize::decode_chain(
186
0
                &serde_json::to_string(chain).unwrap(),
187
0
                block_number_bytes,
188
0
            )
189
0
            .map_err(|_| ())?
Unexecuted instantiation: _RNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_databases0_0B5_
Unexecuted instantiation: _RNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_databases0_0B5_
190
            .chain_information,
191
        ),
192
0
        None => None,
193
    };
194
195
    // Nodes that fail to decode are simply ignored. This is especially important for
196
    // multiaddresses, as the definition of a valid or invalid multiaddress might change across
197
    // versions.
198
0
    let known_nodes = decoded
199
0
        .nodes
200
0
        .iter()
201
0
        .filter_map(|(peer_id, addrs)| {
202
0
            let addrs = addrs
203
0
                .iter()
204
0
                .filter_map(|a| a.parse::<multiaddr::Multiaddr>().ok())
Unexecuted instantiation: _RNCNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_databases1_00B7_
Unexecuted instantiation: _RNCNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_databases1_00B7_
205
0
                .collect();
206
0
            Some((peer_id.parse::<PeerId>().ok()?, addrs))
207
0
        })
Unexecuted instantiation: _RNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_databases1_0B5_
Unexecuted instantiation: _RNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_databases1_0B5_
208
0
        .collect::<Vec<_>>();
209
210
0
    let runtime_code_hint = match (
211
0
        decoded.code_merkle_value,
212
0
        decoded.code_storage_value,
213
0
        decoded.code_closest_ancestor_excluding,
214
    ) {
215
0
        (Some(mv), Some(sv), Some(an)) => Some(DatabaseContentRuntimeCodeHint {
216
0
            code: base64::Engine::decode(&base64::engine::general_purpose::STANDARD_NO_PAD, sv)
217
0
                .map_err(|_| ())?,
Unexecuted instantiation: _RNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_databases2_0B5_
Unexecuted instantiation: _RNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_databases2_0B5_
218
0
            code_merkle_value: hex::decode(mv).map_err(|_| ())?,
Unexecuted instantiation: _RNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_databases3_0B5_
Unexecuted instantiation: _RNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_databases3_0B5_
219
0
            closest_ancestor_excluding: an
220
0
                .as_bytes()
221
0
                .iter()
222
0
                .map(|char| Nibble::from_ascii_hex_digit(*char).ok_or(()))
Unexecuted instantiation: _RNCNvNtCsiGub1lfKphe_13smoldot_light8database15decode_databases4_0B5_
Unexecuted instantiation: _RNCNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_databases4_0B5_
223
0
                .collect::<Result<Vec<Nibble>, ()>>()?,
224
        }),
225
        // A combination of `Some` and `None` is technically invalid, but we simply ignore this
226
        // situation.
227
0
        _ => None,
228
    };
229
230
0
    Ok(DatabaseContent {
231
0
        genesis_block_hash,
232
0
        chain_information,
233
0
        known_nodes,
234
0
        runtime_code_hint,
235
0
    })
236
0
}
Unexecuted instantiation: _RNvNtCsiGub1lfKphe_13smoldot_light8database15decode_database
Unexecuted instantiation: _RNvNtCsih6EgvAwZF2_13smoldot_light8database15decode_database
237
238
0
#[derive(serde::Serialize, serde::Deserialize)]
Unexecuted instantiation: _RNvXNvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtB7_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB2_14___FieldVisitorNtB1c_7Visitor9expecting
Unexecuted instantiation: _RINvXNvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtB8_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1d_7Visitor9visit_u64pEBa_
Unexecuted instantiation: _RINvXNvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtB8_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1d_7Visitor9visit_strpEBa_
Unexecuted instantiation: _RINvXNvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtB8_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1d_7Visitor11visit_bytespEBa_
Unexecuted instantiation: _RINvXs_NvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtBa_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_7___FieldB1d_11deserializepEBc_
Unexecuted instantiation: _RNvXs0_NvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtBa_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_9___VisitorNtB1f_7Visitor9expecting
Unexecuted instantiation: _RINvXs0_NvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtBb_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1g_7Visitor9visit_seqpEBd_
Unexecuted instantiation: _RINvXs0_NvXNvNtCsiGub1lfKphe_13smoldot_light8databases_1__NtBb_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1g_7Visitor9visit_mappEBd_
Unexecuted instantiation: _RNvXNvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtB7_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB2_14___FieldVisitorNtB1c_7Visitor9expecting
Unexecuted instantiation: _RINvXNvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtB8_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1d_7Visitor9visit_strNtNtCscu7pqq74Vb8_10serde_json5error5ErrorEBa_
Unexecuted instantiation: _RNvXs0_NvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtBa_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_9___VisitorNtB1f_7Visitor9expecting
Unexecuted instantiation: _RINvXNvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtB8_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1d_7Visitor9visit_u64pEBa_
Unexecuted instantiation: _RINvXNvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtB8_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1d_7Visitor11visit_bytespEBa_
Unexecuted instantiation: _RINvXs_NvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtBa_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_7___FieldB1d_11deserializeINtNtCscu7pqq74Vb8_10serde_json2de6MapKeyNtNtB2E_4read7StrReadEEBc_
Unexecuted instantiation: _RINvXs0_NvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtBb_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1g_7Visitor9visit_seqINtNtCscu7pqq74Vb8_10serde_json2de9SeqAccessNtNtB2O_4read7StrReadEEBd_
Unexecuted instantiation: _RINvXs0_NvXNvNtCsih6EgvAwZF2_13smoldot_light8databases_1__NtBb_13SerdeDatabaseNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1g_7Visitor9visit_mapINtNtCscu7pqq74Vb8_10serde_json2de9MapAccessNtNtB2O_4read7StrReadEEBd_
239
struct SerdeDatabase {
240
    /// Hexadecimal-encoded hash of the genesis block header. Has no `0x` prefix.
241
    #[serde(rename = "genesisHash")]
242
    genesis_hash: String,
243
    #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")]
244
    chain: Option<Box<serde_json::value::RawValue>>,
245
    nodes: hashbrown::HashMap<String, Vec<String>, fnv::FnvBuildHasher>,
246
    #[serde(
247
        rename = "runtimeCode",
248
        default = "Default::default",
249
        skip_serializing_if = "Option::is_none"
250
    )]
251
    code_storage_value: Option<String>,
252
    #[serde(
253
        rename = "codeMerkleValue",
254
        default = "Default::default",
255
        skip_serializing_if = "Option::is_none"
256
    )]
257
    code_merkle_value: Option<String>,
258
    #[serde(
259
        rename = "codeClosestAncestor",
260
        default = "Default::default",
261
        skip_serializing_if = "Option::is_none"
262
    )]
263
    code_closest_ancestor_excluding: Option<String>,
264
}