Coverage Report

Created: 2024-05-16 12:16

/__w/smoldot/smoldot/repo/lib/src/executor/trie_root_calculator/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
use super::{trie_root_calculator, Config, InProgress, TrieEntryVersion};
21
use crate::{executor::storage_diff::TrieDiff, trie};
22
use core::{iter, ops};
23
24
use rand::distributions::{Distribution as _, Uniform};
25
26
#[test]
27
1
fn empty_trie_works() {
28
1
    let mut calculation = trie_root_calculator(Config {
29
1
        diff: TrieDiff::empty(),
30
1
        diff_trie_entries_version: TrieEntryVersion::V0,
31
1
        max_trie_recalculation_depth_hint: 8,
32
1
    });
33
34
3
    loop {
35
3
        match calculation {
36
1
            InProgress::Finished { trie_root_hash } => {
37
1
                assert_eq!(trie_root_hash, trie::EMPTY_BLAKE2_TRIE_MERKLE_VALUE);
38
1
                return;
39
            }
40
1
            InProgress::ClosestDescendant(req) => {
41
1
                calculation = req.inject(None::<iter::Empty<_>>);
42
1
            }
43
1
            InProgress::ClosestDescendantMerkleValue(req) => {
44
1
                calculation = req.resume_unknown();
45
1
            }
46
0
            InProgress::StorageValue(req) => {
47
0
                calculation = req.inject_value(None);
48
0
            }
49
0
            InProgress::TrieNodeInsertUpdateEvent(ev) => calculation = ev.resume(),
50
0
            InProgress::TrieNodeRemoveEvent(ev) => calculation = ev.resume(),
51
        }
52
    }
53
1
}
54
55
#[test]
56
1
fn one_inserted_node_in_diff() {
57
1
    let mut diff = TrieDiff::empty();
58
1
    diff.diff_insert(vec![0xaa, 0xaa], b"foo".to_vec(), ());
59
1
60
1
    let mut calculation = trie_root_calculator(Config {
61
1
        diff,
62
1
        diff_trie_entries_version: TrieEntryVersion::V0,
63
1
        max_trie_recalculation_depth_hint: 8,
64
1
    });
65
66
20
    loop {
67
20
        match calculation {
68
1
            InProgress::Finished { trie_root_hash } => {
69
1
                let expected = trie::trie_node::calculate_merkle_value(
70
1
                    trie::trie_node::Decoded {
71
1
                        children: [None::<&'static [u8]>; 16],
72
1
                        partial_key: trie::bytes_to_nibbles(vec![0xaa, 0xaa].into_iter()),
73
1
                        storage_value: trie::trie_node::StorageValue::Unhashed(b"foo"),
74
1
                    },
75
1
                    trie::HashFunction::Blake2,
76
1
                    true,
77
1
                )
78
1
                .unwrap();
79
1
80
1
                assert_eq!(trie_root_hash, expected.as_ref());
81
1
                return;
82
            }
83
17
            InProgress::ClosestDescendant(req) => {
84
17
                calculation = req.inject(None::<iter::Empty<_>>);
85
17
            }
86
0
            InProgress::ClosestDescendantMerkleValue(req) => {
87
0
                calculation = req.resume_unknown();
88
0
            }
89
1
            InProgress::StorageValue(req) => {
90
1
                calculation = req.inject_value(None);
91
1
            }
92
1
            InProgress::TrieNodeInsertUpdateEvent(ev) => calculation = ev.resume(),
93
0
            InProgress::TrieNodeRemoveEvent(ev) => calculation = ev.resume(),
94
        }
95
    }
96
1
}
97
98
#[test]
99
1
fn fuzzing() {
100
8.84M
    fn uniform_sample(min: u8, max: u8) -> u8 {
101
8.84M
        Uniform::new_inclusive(min, max).sample(&mut rand::thread_rng())
102
8.84M
    }
103
104
    // We run the test multiple times because of randomness.
105
32.7k
    for _ in 0..32768 {
106
        // Create a random trie.
107
        // Each node contains a `Some` with the storage value or `None` for branch nodes, plus its
108
        // Merkle value as `Some` if already calculated.
109
32.7k
        let mut trie_before_diff = trie::trie_structure::TrieStructure::<(
110
32.7k
            Option<(Vec<u8>, TrieEntryVersion)>,
111
32.7k
            Option<trie::trie_node::MerkleValueOutput>,
112
32.7k
        )>::new();
113
32.7k
114
32.7k
        let mut list = vec![Vec::new()];
115
32.7k
        for elem in list.clone().into_iter() {
116
32.7k
            for _ in 0..uniform_sample(0, 4) {
117
65.7k
                let mut elem = elem.clone();
118
98.9k
                for _ in 0..uniform_sample(0, 3) {
119
98.9k
                    elem.push(uniform_sample(0, 255));
120
98.9k
                }
121
65.7k
                list.push(elem);
122
            }
123
        }
124
131k
        for 
elem98.4k
in list {
125
98.4k
            let mut storage_value = Vec::new();
126
1.18M
            for _ in 0..uniform_sample(0, 24) {
127
1.18M
                storage_value.push(uniform_sample(0, 255));
128
1.18M
            }
129
130
98.4k
            let trie_entry_version = if rand::random::<bool>() {
131
49.4k
                TrieEntryVersion::V1
132
            } else {
133
49.0k
                TrieEntryVersion::V0
134
            };
135
136
98.4k
            match trie_before_diff.node(trie::bytes_to_nibbles(elem.iter().copied())) {
137
82.1k
                trie::trie_structure::Entry::Vacant(e) => {
138
82.1k
                    e.insert_storage_value().insert(
139
82.1k
                        (Some((storage_value, trie_entry_version)), None),
140
82.1k
                        (None, None),
141
82.1k
                    );
142
82.1k
                }
143
                trie::trie_structure::Entry::Occupied(
144
0
                    trie::trie_structure::NodeAccess::Branch(mut e),
145
0
                ) => {
146
0
                    *e.user_data() = (Some((storage_value, trie_entry_version)), None);
147
0
                    e.insert_storage_value();
148
0
                }
149
                trie::trie_structure::Entry::Occupied(
150
                    trie::trie_structure::NodeAccess::Storage(_),
151
16.3k
                ) => {}
152
            }
153
        }
154
155
        // Clone the trie and apply modifications to it. These modifications are also registered
156
        // in a diff.
157
32.7k
        let mut trie_after_diff = trie_before_diff.clone();
158
32.7k
        let mut diff = TrieDiff::empty();
159
32.7k
        let diff_trie_entries_version = if rand::random::<bool>() {
160
16.2k
            TrieEntryVersion::V1
161
        } else {
162
16.5k
            TrieEntryVersion::V0
163
        };
164
165
196k
        for _ in 0..5 {
166
163k
            let mut list = vec![Vec::new()];
167
163k
            for elem in list.clone().into_iter() {
168
163k
                for _ in 0..uniform_sample(0, 4) {
169
326k
                    let mut elem = elem.clone();
170
490k
                    for _ in 0..uniform_sample(0, 3) {
171
490k
                        elem.push(uniform_sample(0, 255));
172
490k
                    }
173
326k
                    list.push(elem);
174
                }
175
            }
176
654k
            for 
elem490k
in list {
177
490k
                let mut storage_value = Vec::new();
178
5.89M
                for _ in 0..uniform_sample(0, 24) {
179
5.89M
                    storage_value.push(uniform_sample(0, 255));
180
5.89M
                }
181
182
490k
                match trie_after_diff.node(trie::bytes_to_nibbles(elem.iter().copied())) {
183
                    trie::trie_structure::Entry::Occupied(
184
171k
                        trie::trie_structure::NodeAccess::Storage(mut e),
185
171k
                    ) => {
186
171k
                        if rand::random::<bool>() {
187
85.3k
                            // Update storage value.
188
85.3k
                            *e.user_data() = (
189
85.3k
                                Some((storage_value.clone(), diff_trie_entries_version)),
190
85.3k
                                None,
191
85.3k
                            );
192
85.3k
                            diff.diff_insert(elem, storage_value, ());
193
85.8k
                        } else {
194
85.8k
                            // Erase node.
195
85.8k
                            e.user_data().0 = None;
196
85.8k
                            e.remove();
197
85.8k
                            diff.diff_insert_erase(elem, ());
198
85.8k
                        }
199
                    }
200
                    trie::trie_structure::Entry::Occupied(
201
66.5k
                        trie::trie_structure::NodeAccess::Branch(mut e),
202
66.5k
                    ) => {
203
66.5k
                        *e.user_data() = (
204
66.5k
                            Some((storage_value.clone(), diff_trie_entries_version)),
205
66.5k
                            None,
206
66.5k
                        );
207
66.5k
                        e.insert_storage_value();
208
66.5k
                        diff.diff_insert(elem, storage_value, ());
209
66.5k
                    }
210
252k
                    trie::trie_structure::Entry::Vacant(e) => {
211
252k
                        e.insert_storage_value().insert(
212
252k
                            (
213
252k
                                Some((storage_value.clone(), diff_trie_entries_version)),
214
252k
                                None,
215
252k
                            ),
216
252k
                            (None, None),
217
252k
                        );
218
252k
                        diff.diff_insert(elem, storage_value, ());
219
252k
                    }
220
                }
221
            }
222
        }
223
224
        // Calculate the Merkle values of the nodes of `trie_before_diff` and `trie_after_diff`.
225
65.5k
        for trie in [
&mut trie_before_diff, &mut trie_after_diff32.7k
] {
226
467k
            for node_index in 
trie.iter_ordered().collect::<Vec<_>>().into_iter().rev()65.5k
{
227
467k
                let mut node_access = trie.node_by_index(node_index).unwrap();
228
467k
229
7.48M
                let children = core::array::from_fn::<_, 16, _>(|n| {
230
7.48M
                    node_access
231
7.48M
                        .child(trie::Nibble::try_from(u8::try_from(n).unwrap()).unwrap())
232
7.48M
                        .map(|mut child| 
child.user_data().1.as_ref().unwrap().clone()402k
)
233
7.48M
                });
234
467k
235
467k
                let is_root_node = node_access.is_root_node();
236
467k
                let partial_key = node_access.partial_key().collect::<Vec<_>>().into_iter();
237
238
                // We have to hash the storage value ahead of time if necessary due to borrow
239
                // checking difficulties.
240
467k
                let storage_value_hashed = match node_access.user_data().0.as_ref() {
241
198k
                    Some((v, TrieEntryVersion::V1)) => {
242
198k
                        if v.len() >= 33 {
243
0
                            Some(blake2_rfc::blake2b::blake2b(32, &[], v))
244
                        } else {
245
198k
                            None
246
                        }
247
                    }
248
269k
                    _ => None,
249
                };
250
467k
                let storage_value = match (
251
467k
                    node_access.user_data().0.as_ref(),
252
467k
                    storage_value_hashed.as_ref(),
253
                ) {
254
0
                    (_, Some(storage_value_hashed)) => trie::trie_node::StorageValue::Hashed(
255
0
                        <&[u8; 32]>::try_from(storage_value_hashed.as_bytes()).unwrap(),
256
0
                    ),
257
397k
                    (Some((v, _)), None) => trie::trie_node::StorageValue::Unhashed(&v[..]),
258
69.7k
                    (None, _) => trie::trie_node::StorageValue::None,
259
                };
260
261
467k
                let merkle_value = trie::trie_node::calculate_merkle_value(
262
467k
                    trie::trie_node::Decoded {
263
467k
                        children,
264
467k
                        partial_key,
265
467k
                        storage_value,
266
467k
                    },
267
467k
                    trie::HashFunction::Blake2,
268
467k
                    is_root_node,
269
467k
                )
270
467k
                .unwrap();
271
467k
272
467k
                node_access.into_user_data().1 = Some(merkle_value);
273
            }
274
        }
275
276
        // Build alternative linear versions of the two tries.
277
32.7k
        let mut trie_map = trie_before_diff
278
32.7k
            .iter_ordered()
279
32.7k
            .collect::<Vec<_>>()
280
32.7k
            .into_iter()
281
84.2k
            .map(|n| {
282
84.2k
                (
283
84.2k
                    trie_before_diff
284
84.2k
                        .node_full_key_by_index(n)
285
84.2k
                        .unwrap()
286
84.2k
                        .collect::<Vec<_>>(),
287
84.2k
                    trie_before_diff[n].1.clone().unwrap().as_ref().to_vec(),
288
84.2k
                )
289
84.2k
            })
290
32.7k
            .collect::<hashbrown::HashMap<_, _, fnv::FnvBuildHasher>>();
291
32.7k
        let trie_after_diff_map = trie_after_diff
292
32.7k
            .iter_ordered()
293
32.7k
            .collect::<Vec<_>>()
294
32.7k
            .into_iter()
295
383k
            .map(|n| {
296
383k
                (
297
383k
                    trie_after_diff
298
383k
                        .node_full_key_by_index(n)
299
383k
                        .unwrap()
300
383k
                        .collect::<Vec<_>>(),
301
383k
                    trie_after_diff[n].1.clone().unwrap().as_ref().to_vec(),
302
383k
                )
303
383k
            })
304
32.7k
            .collect::<hashbrown::HashMap<_, _, fnv::FnvBuildHasher>>();
305
306
        // Use the trie_root_calculator to calculate the root of `trie_after_diff`.
307
32.7k
        let obtained_hash = {
308
32.7k
            let mut calculator = trie_root_calculator(Config {
309
32.7k
                diff: diff.clone(),
310
32.7k
                diff_trie_entries_version,
311
32.7k
                max_trie_recalculation_depth_hint: 8,
312
32.7k
            });
313
314
7.50M
            loop {
315
7.50M
                match calculator {
316
32.7k
                    InProgress::Finished { trie_root_hash } => break trie_root_hash,
317
5.87M
                    InProgress::ClosestDescendant(req) => {
318
5.87M
                        let mut next_node = trie_before_diff
319
5.87M
                            .range_iter(
320
5.87M
                                ops::Bound::Included(req.key_as_vec().into_iter()),
321
5.87M
                                ops::Bound::Unbounded::<iter::Empty<trie::Nibble>>,
322
5.87M
                            )
323
5.87M
                            .next();
324
5.87M
                        // Set `next_node` to `None` if it isn't a descendant of the demanded key.
325
5.87M
                        if next_node.map_or(false, |n| {
326
2.87M
                            !trie_before_diff
327
2.87M
                                .node_full_key_by_index(n)
328
2.87M
                                .unwrap()
329
2.87M
                                .collect::<Vec<_>>()
330
2.87M
                                .starts_with(&req.key_as_vec())
331
5.87M
                        }) {
332
2.79M
                            next_node = None;
333
3.07M
                        }
334
5.87M
                        calculator = req.inject(
335
5.87M
                            next_node.map(|n| 
trie_before_diff.node_full_key_by_index(n).unwrap()83.5k
),
336
5.87M
                        );
337
                    }
338
869k
                    InProgress::ClosestDescendantMerkleValue(req) => {
339
869k
                        if rand::random::<bool>() {
340
434k
                            let mut next_node = trie_before_diff
341
434k
                                .range_iter(
342
434k
                                    ops::Bound::Included(req.key_as_vec().into_iter()),
343
434k
                                    ops::Bound::Unbounded::<iter::Empty<trie::Nibble>>,
344
434k
                                )
345
434k
                                .next();
346
434k
                            // Set `next_node` to `None` if it isn't a descendant of the demanded key.
347
434k
                            if next_node.map_or(false, |n| {
348
219k
                                !trie_before_diff
349
219k
                                    .node_full_key_by_index(n)
350
219k
                                    .unwrap()
351
219k
                                    .collect::<Vec<_>>()
352
219k
                                    .starts_with(&req.key_as_vec())
353
434k
                            }) {
354
203k
                                next_node = None;
355
231k
                            }
356
357
434k
                            if let Some(
next_node16.2k
) = next_node {
358
16.2k
                                calculator = req.inject_merkle_value(
359
16.2k
                                    trie_before_diff
360
16.2k
                                        .node_by_index(next_node)
361
16.2k
                                        .unwrap()
362
16.2k
                                        .into_user_data()
363
16.2k
                                        .1
364
16.2k
                                        .as_ref()
365
16.2k
                                        .unwrap()
366
16.2k
                                        .clone()
367
16.2k
                                        .as_ref(),
368
16.2k
                                );
369
418k
                            } else {
370
418k
                                calculator = req.resume_unknown();
371
418k
                            }
372
                        } else {
373
435k
                            calculator = req.resume_unknown()
374
                        }
375
                    }
376
366k
                    InProgress::StorageValue(req) => {
377
366k
                        let value = if let trie::trie_structure::Entry::Occupied(
378
65.2k
                            trie::trie_structure::NodeAccess::Storage(e),
379
366k
                        ) = trie_before_diff.node(req.key_as_vec().into_iter())
380
                        {
381
65.2k
                            Some(e.into_user_data().0.as_ref().unwrap().clone())
382
                        } else {
383
300k
                            None
384
                        };
385
386
366k
                        calculator =
387
366k
                            req.inject_value(value.as_ref().map(|(val, vers)| 
(&val[..], *vers)65.2k
));
388
                    }
389
365k
                    InProgress::TrieNodeInsertUpdateEvent(ev) => {
390
365k
                        trie_map.insert(
391
365k
                            ev.key()
392
365k
                                .flat_map(crate::util::as_ref_iter)
393
365k
                                .collect::<Vec<_>>(),
394
365k
                            ev.merkle_value().to_vec(),
395
365k
                        );
396
365k
                        calculator = ev.resume();
397
365k
                    }
398
105
                    InProgress::TrieNodeRemoveEvent(ev) => {
399
105
                        let was_in = trie_map.remove(
400
105
                            &ev.key()
401
105
                                .flat_map(crate::util::as_ref_iter)
402
105
                                .collect::<Vec<_>>(),
403
105
                        );
404
105
                        assert!(was_in.is_some());
405
105
                        calculator = ev.resume();
406
                    }
407
                }
408
            }
409
        };
410
411
        // Actual test is here.
412
32.7k
        let expected_hash = trie_after_diff
413
32.7k
            .root_user_data()
414
32.7k
            .map(|n| 
*<&[u8; 32]>::try_from(n.1.as_ref().unwrap().as_ref()).unwrap()32.7k
)
415
32.7k
            .unwrap_or(trie::EMPTY_BLAKE2_TRIE_MERKLE_VALUE);
416
32.7k
        if obtained_hash != expected_hash {
417
0
            panic!(
418
0
                "\nexpected = {:?}\ncalculated = {:?}\ntrie_before = {:?}\ndiff = {:?}",
419
0
                expected_hash, obtained_hash, trie_before_diff, diff
420
0
            );
421
32.7k
        }
422
32.7k
        if trie_map != trie_after_diff_map {
423
0
            panic!(
424
0
                "\nexpected = {:?}\ncalculated = {:?}\ntrie_before = {:?}\ndiff = {:?}",
425
0
                trie_after_diff_map, trie_map, trie_before_diff, diff
426
0
            );
427
32.7k
        }
428
    }
429
1
}