/__w/smoldot/smoldot/repo/lib/src/executor/runtime_call/tests.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2023 Pierre Krieger |
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 | | #![cfg(test)] |
19 | | |
20 | | //! The test in this module reads various JSON files containing test fixtures and executes them. |
21 | | //! |
22 | | //! Each test fixture contains a block (header and body), plus the storage of its parent. The |
23 | | //! test consists in executing the block, to make sure that the state trie root matches the one |
24 | | //! calculated by smoldot. |
25 | | |
26 | | use core::{iter, ops}; |
27 | | |
28 | | use super::{run, Config, RuntimeCall, StorageProofSizeBehavior}; |
29 | | use crate::{executor::host, trie}; |
30 | | use alloc::collections::BTreeMap; |
31 | | |
32 | | #[test] |
33 | 1 | fn execute_blocks() { |
34 | | // Tests ordered alphabetically. |
35 | 4 | for (test_num, test_json) in [ |
36 | 1 | include_str!("./child-trie-create-multiple.json"), |
37 | 1 | include_str!("./child-trie-create-one.json"), |
38 | 1 | include_str!("./child-trie-destroy.json"), |
39 | 1 | include_str!("./child-trie-read-basic.json"), |
40 | 1 | // TODO: more tests? |
41 | 1 | ] |
42 | 1 | .into_iter() |
43 | 1 | .enumerate() |
44 | | { |
45 | | // Decode the test JSON. |
46 | 4 | let test_data = serde_json::from_str::<Test>(test_json).unwrap(); |
47 | | |
48 | | // Turn the nice-looking data into something with better access times. |
49 | 4 | let storage = { |
50 | 4 | let mut storage = test_data |
51 | 4 | .parent_storage |
52 | 4 | .main_trie |
53 | 4 | .iter() |
54 | 217 | .map(|(key, value)| ((None, key.0.clone()), value.0.clone())) |
55 | 4 | .collect::<BTreeMap<_, _>>(); |
56 | 11 | for (child_trie, child_trie_data7 ) in &test_data.parent_storage.child_tries { |
57 | 14 | for (key, value7 ) in child_trie_data { |
58 | 7 | storage.insert((Some(child_trie.0.clone()), key.0.clone()), value.0.clone()); |
59 | 7 | } |
60 | | } |
61 | 4 | storage |
62 | 4 | }; |
63 | 4 | |
64 | 4 | // Build the runtime. |
65 | 4 | let virtual_machine = { |
66 | 4 | let code = storage |
67 | 4 | .get(&(None, b":code".to_vec())) |
68 | 4 | .expect("no runtime code found"); |
69 | 4 | let heap_pages = crate::executor::storage_heap_pages_to_value( |
70 | 4 | storage.get(&(None, b":heappages".to_vec())).map(|v| &v[..]0 ), |
71 | 4 | ) |
72 | 4 | .unwrap(); |
73 | 4 | |
74 | 4 | host::HostVmPrototype::new(host::Config { |
75 | 4 | module: code, |
76 | 4 | heap_pages, |
77 | 4 | exec_hint: crate::executor::vm::ExecHint::ExecuteOnceWithNonDeterministicValidation, |
78 | 4 | allow_unresolved_imports: false, |
79 | 4 | }) |
80 | 4 | .unwrap() |
81 | 4 | }; |
82 | 4 | |
83 | 4 | // The runtime indicates the version of the trie items of the parent storage. |
84 | 4 | // While in principle each storage item could have a different version, in practice we |
85 | 4 | // just assume they're all the same. |
86 | 4 | let state_version = virtual_machine |
87 | 4 | .runtime_version() |
88 | 4 | .decode() |
89 | 4 | .state_version |
90 | 4 | .unwrap_or(host::TrieEntryVersion::V0); |
91 | 4 | |
92 | 4 | // Start executing `Core_execute_block`. This runtime call will verify at the end whether |
93 | 4 | // the trie root hash of the block matches the one calculated by smoldot. |
94 | 4 | let mut execution = run(Config { |
95 | 4 | virtual_machine, |
96 | 4 | function_to_call: "Core_execute_block", |
97 | 4 | max_log_level: 3, |
98 | 4 | storage_proof_size_behavior: StorageProofSizeBehavior::Unimplemented, |
99 | 4 | storage_main_trie_changes: Default::default(), |
100 | 4 | calculate_trie_changes: false, |
101 | 4 | parameter: { |
102 | 4 | // Block header + number of extrinsics + extrinsics |
103 | 4 | let encoded_body_len = |
104 | 4 | crate::util::encode_scale_compact_usize(test_data.block.body.len()); |
105 | 4 | iter::once(either::Right(either::Left(&test_data.block.header.0))) |
106 | 4 | .chain(iter::once(either::Right(either::Right(encoded_body_len)))) |
107 | 18 | .chain(test_data.block.body.iter().map(|b| either::Left(&b.0))) |
108 | 4 | }, |
109 | 4 | }) |
110 | 4 | .unwrap(); |
111 | | |
112 | | loop { |
113 | 4 | match execution { |
114 | 4 | RuntimeCall::Finished(Ok(_)) => break, // Test successful! |
115 | 0 | RuntimeCall::Finished(Err(err)) => { |
116 | 0 | panic!("Error during test #{}: {:?}", test_num, err) |
117 | | } |
118 | 5 | RuntimeCall::SignatureVerification(sig) => execution = sig.verify_and_resume(), |
119 | 4.90k | RuntimeCall::ClosestDescendantMerkleValue(req) => execution = req.resume_unknown(), |
120 | 449 | RuntimeCall::StorageGet(get) => { |
121 | 449 | let value = storage |
122 | 449 | .get(&( |
123 | 449 | get.child_trie().map(|c| c.as_ref().to_owned()11 ), |
124 | 449 | get.key().as_ref().to_owned(), |
125 | 449 | )) |
126 | 449 | .map(|v| (iter::once(&v[..]), state_version)289 ); |
127 | 449 | execution = get.inject_value(value); |
128 | 449 | } |
129 | 6.05k | RuntimeCall::NextKey(req) => { |
130 | 6.05k | // Because `NextKey` might ask for branch nodes, and that we don't build the |
131 | 6.05k | // trie in its entirety, we have to use an algorithm that finds the branch |
132 | 6.05k | // nodes for us. |
133 | 6.05k | let next_key = { |
134 | 6.05k | let mut search = trie::branch_search::BranchSearch::NextKey( |
135 | 6.05k | trie::branch_search::start_branch_search(trie::branch_search::Config { |
136 | 6.05k | key_before: req.key().collect::<Vec<_>>().into_iter(), |
137 | 6.05k | or_equal: req.or_equal(), |
138 | 6.05k | prefix: req.prefix().collect::<Vec<_>>().into_iter(), |
139 | 6.05k | no_branch_search: !req.branch_nodes(), |
140 | 6.05k | }), |
141 | 6.05k | ); |
142 | | |
143 | 12.5k | loop { |
144 | 12.5k | match search { |
145 | | trie::branch_search::BranchSearch::Found { |
146 | 6.05k | branch_trie_node_key, |
147 | 6.05k | } => break branch_trie_node_key, |
148 | 6.52k | trie::branch_search::BranchSearch::NextKey(bs_req) => { |
149 | 6.52k | let result = storage |
150 | 6.52k | .range(( |
151 | 6.52k | if bs_req.or_equal() { |
152 | 6.51k | ops::Bound::Included(( |
153 | 6.51k | req.child_trie().map(|c| c.as_ref().to_owned()88 ), |
154 | 6.51k | bs_req.key_before().collect::<Vec<_>>(), |
155 | 6.51k | )) |
156 | | } else { |
157 | 10 | ops::Bound::Excluded(( |
158 | 10 | req.child_trie().map(|c| c.as_ref().to_owned()1 ), |
159 | 10 | bs_req.key_before().collect::<Vec<_>>(), |
160 | 10 | )) |
161 | | }, |
162 | 6.52k | ops::Bound::Unbounded, |
163 | 6.52k | )) |
164 | 6.52k | .next() |
165 | 6.52k | .filter(|((trie, key), _)| { |
166 | 6.44k | *trie == req.child_trie().map(|c| c.as_ref().to_owned()55 ) |
167 | 6.32k | && key.starts_with( |
168 | 6.32k | &bs_req.prefix().collect::<Vec<_>>(), |
169 | 6.32k | ) |
170 | 6.52k | }6.44k ) |
171 | 6.52k | .map(|((_, k), _)| k985 ); |
172 | 6.52k | |
173 | 6.52k | search = bs_req.inject(result.map(|k| k.iter().copied()985 )); |
174 | | } |
175 | | } |
176 | | } |
177 | | }; |
178 | | |
179 | 6.05k | execution = req.inject_key(next_key.map(|nk| nk.into_iter()343 )); |
180 | | } |
181 | 0 | RuntimeCall::LogEmit(log) => execution = log.resume(), |
182 | | RuntimeCall::OffchainStorageSet(_) | RuntimeCall::Offchain(_) => { |
183 | 0 | unimplemented!() |
184 | | } |
185 | | } |
186 | | } |
187 | | } |
188 | 1 | } |
189 | | |
190 | | // Serde structs used to decode the test fixtures. |
191 | | |
192 | 20 | #[derive(s12 erde::Deserialize)] _RINvXs_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtBa_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_7___FieldB1h_11deserializeINtNtCscu7pqq74Vb8_10serde_json2de6MapKeyNtNtB2I_4read7StrReadEEBg_ Line | Count | Source | 192 | 8 | #[derive(serde::Deserialize)] |
Unexecuted instantiation: _RINvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtBb_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1k_7Visitor9visit_seqINtNtCscu7pqq74Vb8_10serde_json2de9SeqAccessNtNtB2S_4read7StrReadEEBh_ _RINvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtBb_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1k_7Visitor9visit_mapINtNtCscu7pqq74Vb8_10serde_json2de9MapAccessNtNtB2S_4read7StrReadEEBh_ Line | Count | Source | 192 | 12 | #[derive(s4 erde::Deserialize)] |
_RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtB8_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1h_7Visitor9visit_strNtNtCscu7pqq74Vb8_10serde_json5error5ErrorEBe_ Line | Count | Source | 192 | 8 | #[derive(serde::Deserialize)] |
Unexecuted instantiation: _RNvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtBa_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_9___VisitorNtB1j_7Visitor9expecting Unexecuted instantiation: _RNvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtB7_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB2_14___FieldVisitorNtB1g_7Visitor9expecting Unexecuted instantiation: _RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtB8_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1h_7Visitor9visit_u64pEBe_ Unexecuted instantiation: _RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5tests1__NtB8_4TestNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1h_7Visitor11visit_bytespEBe_ |
193 | | struct Test { |
194 | | block: Block, |
195 | | #[serde(rename = "parentStorage")] |
196 | | parent_storage: Storage, |
197 | | } |
198 | | |
199 | 20 | #[derive(s12 erde::Deserialize)] _RINvXs_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtBa_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_7___FieldB1k_11deserializeINtNtCscu7pqq74Vb8_10serde_json2de6MapKeyNtNtB2L_4read7StrReadEEBg_ Line | Count | Source | 199 | 8 | #[derive(serde::Deserialize)] |
Unexecuted instantiation: _RINvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtBb_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1n_7Visitor9visit_seqINtNtCscu7pqq74Vb8_10serde_json2de9SeqAccessNtNtB2V_4read7StrReadEEBh_ _RINvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtBb_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1n_7Visitor9visit_mapINtNtCscu7pqq74Vb8_10serde_json2de9MapAccessNtNtB2V_4read7StrReadEEBh_ Line | Count | Source | 199 | 12 | #[derive(s4 erde::Deserialize)] |
_RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtB8_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1k_7Visitor9visit_strNtNtCscu7pqq74Vb8_10serde_json5error5ErrorEBe_ Line | Count | Source | 199 | 8 | #[derive(serde::Deserialize)] |
Unexecuted instantiation: _RNvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtBa_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_9___VisitorNtB1m_7Visitor9expecting Unexecuted instantiation: _RNvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtB7_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB2_14___FieldVisitorNtB1j_7Visitor9expecting Unexecuted instantiation: _RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtB8_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1k_7Visitor9visit_u64pEBe_ Unexecuted instantiation: _RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss_1__NtB8_5BlockNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1k_7Visitor11visit_bytespEBe_ |
200 | | struct Block { |
201 | | header: HexString, |
202 | | body: Vec<HexString>, |
203 | | } |
204 | | |
205 | 20 | #[derive(s12 erde::Deserialize)] _RINvXs_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtBa_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_7___FieldB1n_11deserializeINtNtCscu7pqq74Vb8_10serde_json2de6MapKeyNtNtB2O_4read7StrReadEEBg_ Line | Count | Source | 205 | 8 | #[derive(serde::Deserialize)] |
Unexecuted instantiation: _RINvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtBb_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1q_7Visitor9visit_seqINtNtCscu7pqq74Vb8_10serde_json2de9SeqAccessNtNtB2Y_4read7StrReadEEBh_ _RINvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtBb_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB6_9___VisitorNtB1q_7Visitor9visit_mapINtNtCscu7pqq74Vb8_10serde_json2de9MapAccessNtNtB2Y_4read7StrReadEEBh_ Line | Count | Source | 205 | 12 | #[derive(s4 erde::Deserialize)] |
_RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtB8_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1n_7Visitor9visit_strNtNtCscu7pqq74Vb8_10serde_json5error5ErrorEBe_ Line | Count | Source | 205 | 8 | #[derive(serde::Deserialize)] |
Unexecuted instantiation: _RNvXs0_NvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtBa_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB5_9___VisitorNtB1p_7Visitor9expecting Unexecuted instantiation: _RNvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtB7_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB2_14___FieldVisitorNtB1m_7Visitor9expecting Unexecuted instantiation: _RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtB8_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1n_7Visitor9visit_u64pEBe_ Unexecuted instantiation: _RINvXNvXNvNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testss0_1__NtB8_7StorageNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeNtB3_14___FieldVisitorNtB1n_7Visitor11visit_bytespEBe_ |
206 | | struct Storage { |
207 | | #[serde(rename = "mainTrie")] |
208 | | main_trie: hashbrown::HashMap<HexString, HexString>, |
209 | | #[serde(rename = "childTries")] |
210 | | child_tries: hashbrown::HashMap<HexString, hashbrown::HashMap<HexString, HexString>>, |
211 | | } |
212 | | |
213 | | #[derive(Clone, PartialEq, Eq, Hash)] |
214 | | struct HexString(Vec<u8>); |
215 | | |
216 | | impl<'a> serde::Deserialize<'a> for HexString { |
217 | 468 | fn deserialize<D>(deserializer: D) -> Result<HexString, D::Error> |
218 | 468 | where |
219 | 468 | D: serde::Deserializer<'a>, |
220 | 468 | { |
221 | 468 | let string = String::deserialize(deserializer)?0 ; |
222 | | |
223 | 468 | if string.is_empty() { |
224 | 0 | return Ok(HexString(Vec::new())); |
225 | 468 | } |
226 | 468 | |
227 | 468 | if !string.starts_with("0x") { |
228 | 0 | return Err(serde::de::Error::custom( |
229 | 0 | "hexadecimal string doesn't start with 0x", |
230 | 0 | )); |
231 | 468 | } |
232 | | |
233 | 468 | let bytes = hex::decode(&string[2..]).map_err(serde::de::Error::custom)?0 ; |
234 | 468 | Ok(HexString(bytes)) |
235 | 468 | } _RINvXNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testsNtB3_9HexStringNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeINtNtCscu7pqq74Vb8_10serde_json2de6MapKeyNtNtB28_4read7StrReadEEB9_ Line | Count | Source | 217 | 231 | fn deserialize<D>(deserializer: D) -> Result<HexString, D::Error> | 218 | 231 | where | 219 | 231 | D: serde::Deserializer<'a>, | 220 | 231 | { | 221 | 231 | let string = String::deserialize(deserializer)?0 ; | 222 | | | 223 | 231 | if string.is_empty() { | 224 | 0 | return Ok(HexString(Vec::new())); | 225 | 231 | } | 226 | 231 | | 227 | 231 | if !string.starts_with("0x") { | 228 | 0 | return Err(serde::de::Error::custom( | 229 | 0 | "hexadecimal string doesn't start with 0x", | 230 | 0 | )); | 231 | 231 | } | 232 | | | 233 | 231 | let bytes = hex::decode(&string[2..]).map_err(serde::de::Error::custom)?0 ; | 234 | 231 | Ok(HexString(bytes)) | 235 | 231 | } |
Unexecuted instantiation: _RINvXNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testsNtB3_9HexStringNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeINtNvNtNtB1g_9___private2de13missing_field24MissingFieldDeserializerNtNtCscu7pqq74Vb8_10serde_json5error5ErrorEEB9_ _RINvXNtNtNtCsN16ciHI6Qf_7smoldot8executor12runtime_call5testsNtB3_9HexStringNtNtCsf0yC2YK6bpM_5serde2de11Deserialize11deserializeQINtNtCscu7pqq74Vb8_10serde_json2de12DeserializerNtNtB29_4read7StrReadEEB9_ Line | Count | Source | 217 | 237 | fn deserialize<D>(deserializer: D) -> Result<HexString, D::Error> | 218 | 237 | where | 219 | 237 | D: serde::Deserializer<'a>, | 220 | 237 | { | 221 | 237 | let string = String::deserialize(deserializer)?0 ; | 222 | | | 223 | 237 | if string.is_empty() { | 224 | 0 | return Ok(HexString(Vec::new())); | 225 | 237 | } | 226 | 237 | | 227 | 237 | if !string.starts_with("0x") { | 228 | 0 | return Err(serde::de::Error::custom( | 229 | 0 | "hexadecimal string doesn't start with 0x", | 230 | 0 | )); | 231 | 237 | } | 232 | | | 233 | 237 | let bytes = hex::decode(&string[2..]).map_err(serde::de::Error::custom)?0 ; | 234 | 237 | Ok(HexString(bytes)) | 235 | 237 | } |
|
236 | | } |