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}