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