dbwrapper.h raw
1 // Copyright (c) 2012-2022 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #ifndef LIMENKA_DBWRAPPER_H
6 #define LIMENKA_DBWRAPPER_H
7
8 #include <attributes.h>
9 #include <serialize.h>
10 #include <span.h>
11 #include <streams.h>
12 #include <util/check.h>
13 #include <util/fs.h>
14 #include <util/result.h>
15
16 #include <cstddef>
17 #include <exception>
18 #include <memory>
19 #include <optional>
20 #include <stdexcept>
21 #include <string>
22 #include <vector>
23
24 util::Result<void> dbwrapper_SanityCheck();
25
26 static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64;
27 static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024;
28 static const size_t DBWRAPPER_MAX_FILE_SIZE = 32 << 20; // 32 MiB
29
30 static constexpr size_t DEFAULT_DB_FILE_SIZE{64};
31
32 //! User-controlled performance and debug options.
33 struct DBOptions {
34 //! Compact database on startup.
35 bool force_compact = false;
36 //! Target size of files.
37 size_t max_file_size{DEFAULT_DB_FILE_SIZE << 20};
38 };
39
40 //! Application-specific storage settings.
41 struct DBParams {
42 //! Location in the filesystem where leveldb data will be stored.
43 fs::path path;
44 //! Configures various leveldb cache settings.
45 size_t cache_bytes;
46 //! If true, use leveldb's memory environment.
47 bool memory_only = false;
48 //! If true, remove all existing data.
49 bool wipe_data = false;
50 //! If true, store data obfuscated via simple XOR. If false, XOR with a
51 //! zero'd byte array.
52 bool obfuscate = false;
53 //! Passed-through options.
54 DBOptions options{};
55 };
56
57 class dbwrapper_error : public std::runtime_error
58 {
59 public:
60 explicit dbwrapper_error(const std::string& msg) : std::runtime_error(msg) {}
61 };
62
63 class CDBWrapper;
64
65 /** These should be considered an implementation detail of the specific database.
66 */
67 namespace dbwrapper_private {
68
69 /** Work around circular dependency, as well as for testing in dbwrapper_tests.
70 * Database obfuscation should be considered an implementation detail of the
71 * specific database.
72 */
73 const Obfuscation& GetObfuscateKey(const CDBWrapper&);
74
75 }; // namespace dbwrapper_private
76
77 bool DestroyDB(const std::string& path_str);
78
79 /** Batch of changes queued to be written to a CDBWrapper */
80 class CDBBatch
81 {
82 friend class CDBWrapper;
83
84 private:
85 static constexpr size_t kHeader{12}; // See: src/leveldb/db/write_batch.cc#L27
86
87 const CDBWrapper &parent;
88
89 struct WriteBatchImpl;
90 const std::unique_ptr<WriteBatchImpl> m_impl_batch;
91
92 DataStream ssKey{};
93 DataStream ssValue{};
94
95 size_t size_estimate{0};
96
97 void WriteImpl(Span<const std::byte> key, DataStream& ssValue);
98 void EraseImpl(Span<const std::byte> key);
99
100 public:
101 /**
102 * @param[in] _parent CDBWrapper that this batch is to be submitted to
103 */
104 explicit CDBBatch(const CDBWrapper& _parent);
105 ~CDBBatch();
106 void Clear();
107
108 template <typename K, typename V>
109 void Write(const K& key, const V& value)
110 {
111 ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
112 ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE);
113 ssKey << key;
114 ssValue << value;
115 WriteImpl(ssKey, ssValue);
116 ssKey.clear();
117 ssValue.clear();
118 }
119
120 template <typename K>
121 void Erase(const K& key)
122 {
123 ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
124 ssKey << key;
125 EraseImpl(ssKey);
126 ssKey.clear();
127 }
128
129 size_t SizeEstimate() const { return size_estimate; }
130 };
131
132 class CDBIterator
133 {
134 public:
135 struct IteratorImpl;
136
137 private:
138 const CDBWrapper &parent;
139 const std::unique_ptr<IteratorImpl> m_impl_iter;
140
141 void SeekImpl(Span<const std::byte> key);
142 Span<const std::byte> GetKeyImpl() const;
143 Span<const std::byte> GetValueImpl() const;
144
145 public:
146
147 /**
148 * @param[in] _parent Parent CDBWrapper instance.
149 * @param[in] _piter The original leveldb iterator.
150 */
151 CDBIterator(const CDBWrapper& _parent, std::unique_ptr<IteratorImpl> _piter);
152 ~CDBIterator();
153
154 bool Valid() const;
155
156 void SeekToFirst();
157
158 template<typename K> void Seek(const K& key) {
159 DataStream ssKey{};
160 ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
161 ssKey << key;
162 SeekImpl(ssKey);
163 }
164
165 void Next();
166
167 template<typename K> bool GetKey(K& key) {
168 try {
169 DataStream ssKey{GetKeyImpl()};
170 ssKey >> key;
171 } catch (const std::exception&) {
172 return false;
173 }
174 return true;
175 }
176
177 template<typename V> bool GetValue(V& value) {
178 try {
179 DataStream ssValue{GetValueImpl()};
180 ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent));
181 ssValue >> value;
182 } catch (const std::exception&) {
183 return false;
184 }
185 return true;
186 }
187 };
188
189 struct LevelDBContext;
190
191 class CDBWrapper
192 {
193 friend const Obfuscation& dbwrapper_private::GetObfuscateKey(const CDBWrapper&);
194 private:
195 //! holds all leveldb-specific fields of this class
196 std::unique_ptr<LevelDBContext> m_db_context;
197
198 //! the name of this database
199 std::string m_name;
200
201 //! optional XOR-obfuscation of the database
202 Obfuscation obfuscate_key;
203
204 //! the key under which the obfuscation key is stored
205 static const std::string OBFUSCATE_KEY_KEY;
206
207 std::vector<unsigned char> CreateObfuscateKey() const;
208
209 //! path to filesystem storage
210 const fs::path m_path;
211
212 //! whether or not the database resides in memory
213 bool m_is_memory;
214
215 std::optional<std::string> ReadImpl(Span<const std::byte> key) const;
216 bool ExistsImpl(Span<const std::byte> key) const;
217 size_t EstimateSizeImpl(Span<const std::byte> key1, Span<const std::byte> key2) const;
218 auto& DBContext() const LIFETIMEBOUND { return *Assert(m_db_context); }
219
220 public:
221 CDBWrapper(const DBParams& params);
222 ~CDBWrapper();
223
224 CDBWrapper(const CDBWrapper&) = delete;
225 CDBWrapper& operator=(const CDBWrapper&) = delete;
226
227 template <typename K, typename V>
228 bool Read(const K& key, V& value) const
229 {
230 DataStream ssKey{};
231 ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
232 ssKey << key;
233 std::optional<std::string> strValue{ReadImpl(ssKey)};
234 if (!strValue) {
235 return false;
236 }
237 try {
238 DataStream ssValue{MakeByteSpan(*strValue)};
239 ssValue.Xor(obfuscate_key);
240 ssValue >> value;
241 } catch (const std::exception&) {
242 return false;
243 }
244 return true;
245 }
246
247 template <typename K, typename V>
248 bool Write(const K& key, const V& value, bool fSync = false)
249 {
250 CDBBatch batch(*this);
251 batch.Write(key, value);
252 return WriteBatch(batch, fSync);
253 }
254
255 //! @returns filesystem path to the on-disk data.
256 std::optional<fs::path> StoragePath() {
257 if (m_is_memory) {
258 return {};
259 }
260 return m_path;
261 }
262
263 template <typename K>
264 bool Exists(const K& key) const
265 {
266 DataStream ssKey{};
267 ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
268 ssKey << key;
269 return ExistsImpl(ssKey);
270 }
271
272 template <typename K>
273 bool Erase(const K& key, bool fSync = false)
274 {
275 CDBBatch batch(*this);
276 batch.Erase(key);
277 return WriteBatch(batch, fSync);
278 }
279
280 bool WriteBatch(CDBBatch& batch, bool fSync = false);
281
282 // Get an estimate of LevelDB memory usage (in bytes).
283 size_t DynamicMemoryUsage() const;
284
285 CDBIterator* NewIterator();
286
287 /**
288 * Return true if the database managed by this class contains no entries.
289 */
290 bool IsEmpty();
291
292 template<typename K>
293 size_t EstimateSize(const K& key_begin, const K& key_end) const
294 {
295 DataStream ssKey1{}, ssKey2{};
296 ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
297 ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
298 ssKey1 << key_begin;
299 ssKey2 << key_end;
300 return EstimateSizeImpl(ssKey1, ssKey2);
301 }
302 };
303
304 #endif // LIMENKA_DBWRAPPER_H
305