cloud_mmr/storage/
leaf_set.rs

1// Copyright 2021 The Grin Developers
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! The Grin leaf_set implementation.
15//! Compact (roaring) bitmap representing the set of leaf positions
16//! that exist and are not currently pruned in the MMR.
17
18use crate::pmmr;
19use crate::storage::prune_list::PruneList;
20use crate::storage::{read_bitmap, save_via_temp_file};
21use croaring::Bitmap;
22use std::io::{self, Write};
23use std::path::{Path, PathBuf};
24
25/// Compact (roaring) bitmap representing the set of positions of
26/// leaves that are currently unpruned in the MMR.
27pub struct LeafSet {
28    path: PathBuf,
29    bitmap: Bitmap,
30    bitmap_bak: Bitmap,
31}
32
33impl LeafSet {
34    /// Open the remove log file.
35    /// The content of the file will be read in memory for fast checking.
36    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<LeafSet> {
37        let file_path = path.as_ref();
38        let bitmap = if file_path.exists() {
39            read_bitmap(&file_path)?
40        } else {
41            Bitmap::create()
42        };
43
44        if !bitmap.is_empty() {
45            debug!(
46                "bitmap {} pos ({} bytes)",
47                bitmap.cardinality(),
48                bitmap.get_serialized_size_in_bytes(),
49            );
50        }
51
52        Ok(LeafSet {
53            path: file_path.to_path_buf(),
54            bitmap_bak: bitmap.clone(),
55            bitmap,
56        })
57    }
58
59    /// Copies a snapshot of the utxo file into the primary utxo file.
60    pub fn copy_snapshot<P: AsRef<Path>>(path: P, cp_path: P) -> io::Result<()> {
61        let cp_file_path = cp_path.as_ref();
62
63        if !cp_file_path.exists() {
64            debug!(
65                "leaf_set: rewound leaf file not found: {}",
66                cp_file_path.display()
67            );
68            return Ok(());
69        }
70
71        let bitmap = read_bitmap(&cp_file_path)?;
72        debug!(
73            "leaf_set: copying rewound file {} to {}",
74            cp_file_path.display(),
75            path.as_ref().display()
76        );
77
78        let mut leaf_set = LeafSet {
79            path: path.as_ref().to_path_buf(),
80            bitmap_bak: bitmap.clone(),
81            bitmap,
82        };
83
84        leaf_set.flush()?;
85        Ok(())
86    }
87
88    /// Calculate the set of unpruned leaves
89    /// up to and including the cutoff_pos.
90    /// Only applicable for the output MMR.
91    fn unpruned_pre_cutoff(&self, cutoff_pos: u64, prune_list: &PruneList) -> Bitmap {
92        (1..=cutoff_pos)
93            .filter(|&x| pmmr::is_leaf(x - 1) && !prune_list.is_pruned(x - 1))
94            .map(|x| x as u32)
95            .collect()
96    }
97
98    /// Calculate the set of pruned positions
99    /// up to and including the cutoff_pos.
100    /// Uses both the leaf_set and the prune_list to determine prunedness.
101    pub fn removed_pre_cutoff(
102        &self,
103        cutoff_pos: u64,
104        rewind_rm_pos: &Bitmap,
105        prune_list: &PruneList,
106    ) -> Bitmap {
107        let mut bitmap = self.bitmap.clone();
108
109        // First remove pos from leaf_set that were
110        // added after the point we are rewinding to.
111        let to_remove = ((cutoff_pos + 1) as u32)..bitmap.maximum().unwrap_or(0);
112        bitmap.remove_range_closed(to_remove);
113
114        // Then add back output pos to the leaf_set
115        // that were removed.
116        bitmap.or_inplace(&rewind_rm_pos);
117
118        // Invert bitmap for the leaf pos and return the resulting bitmap.
119        bitmap
120            .flip(1..(cutoff_pos + 1))
121            .and(&self.unpruned_pre_cutoff(cutoff_pos, prune_list))
122    }
123
124    /// Rewinds the leaf_set back to a previous state.
125    /// Removes all pos after the cutoff.
126    /// Adds back all pos in rewind_rm_pos.
127    pub fn rewind(&mut self, cutoff_pos: u64, rewind_rm_pos: &Bitmap) {
128        // First remove pos from leaf_set that were
129        // added after the point we are rewinding to.
130        let to_remove = ((cutoff_pos + 1) as u32)..self.bitmap.maximum().unwrap_or(0);
131        self.bitmap.remove_range_closed(to_remove);
132
133        // Then add back output pos to the leaf_set
134        // that were removed.
135        self.bitmap.or_inplace(&rewind_rm_pos);
136    }
137
138    /// Append a new position to the leaf_set.
139    pub fn add(&mut self, pos0: u64) {
140        self.bitmap.add(1 + pos0 as u32);
141    }
142
143    /// Remove the provided position from the leaf_set.
144    pub fn remove(&mut self, pos0: u64) {
145        self.bitmap.remove(1 + pos0 as u32);
146    }
147
148    /// Flush the leaf_set to file.
149    pub fn flush(&mut self) -> io::Result<()> {
150        // First run the optimization step on the bitmap.
151        self.bitmap.run_optimize();
152
153        // Write the updated bitmap file to disk.
154        save_via_temp_file(&self.path, ".tmp", |file| {
155            file.write_all(&self.bitmap.serialize())
156        })?;
157
158        // Make sure our backup in memory is up to date.
159        self.bitmap_bak = self.bitmap.clone();
160
161        Ok(())
162    }
163
164    /// Discard any pending changes.
165    pub fn discard(&mut self) {
166        self.bitmap = self.bitmap_bak.clone();
167    }
168
169    /// Whether the leaf_set includes the provided position.
170    pub fn includes(&self, pos0: u64) -> bool {
171        self.bitmap.contains(1 + pos0 as u32)
172    }
173
174    /// Number of positions stored in the leaf_set.
175    pub fn len(&self) -> usize {
176        self.bitmap.cardinality() as usize
177    }
178
179    /// Is the leaf_set empty.
180    pub fn is_empty(&self) -> bool {
181        self.len() == 0
182    }
183
184    /// Iterator over positionns in the leaf_set (all leaf positions).
185    pub fn iter(&self) -> impl Iterator<Item = u64> + '_ {
186        self.bitmap.iter().map(|x| x as u64)
187    }
188}