xrpld
Loading...
Searching...
No Matches
NuDBFactory.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/base_uint.h>
3#include <xrpl/basics/contract.h>
4#include <xrpl/beast/core/LexicalCast.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/config/BasicConfig.h>
8#include <xrpl/config/Constants.h>
9#include <xrpl/nodestore/Backend.h>
10#include <xrpl/nodestore/Factory.h>
11#include <xrpl/nodestore/Manager.h>
12#include <xrpl/nodestore/NodeObject.h>
13#include <xrpl/nodestore/Scheduler.h>
14#include <xrpl/nodestore/Types.h>
15#include <xrpl/nodestore/detail/DecodedBlob.h>
16#include <xrpl/nodestore/detail/EncodedBlob.h>
17#include <xrpl/nodestore/detail/codec.h>
18
19#include <boost/filesystem/operations.hpp>
20#include <boost/filesystem/path.hpp>
21#include <boost/system/detail/errc.hpp>
22
23#include <nudb/context.hpp>
24#include <nudb/create.hpp> // IWYU pragma: keep
25#include <nudb/detail/buffer.hpp>
26#include <nudb/error.hpp>
27#include <nudb/file.hpp>
28#include <nudb/progress.hpp>
29#include <nudb/store.hpp>
30#include <nudb/verify.hpp> // IWYU pragma: keep
31#include <nudb/visit.hpp> // IWYU pragma: keep
32#include <nudb/xxhasher.hpp>
33
34#include <atomic>
35#include <chrono>
36#include <cstdint>
37#include <cstdio>
38#include <exception>
39#include <functional>
40#include <memory>
41#include <optional>
42#include <sstream>
43#include <stdexcept>
44#include <string>
45#include <utility>
46
47namespace xrpl::NodeStore {
48
49class NuDBBackend : public Backend
50{
51public:
52 // "appnum" is an application-defined constant stored in the header of a
53 // NuDB database. We used it to identify shard databases before that code
54 // was removed. For now, its only use is a sanity check that the database
55 // was created by xrpld.
56 static constexpr std::uint64_t kAppNum = 1;
57
59 size_t const keyBytes;
63 nudb::store db;
66
68 size_t keyBytes,
69 Section const& keyValues,
72 beast::Journal journal)
73 : j(journal)
76 , name(get(keyValues, Keys::kPath))
77 , blockSize(parseBlockSize(name, keyValues, journal))
78 , deletePath(false)
80 {
81 if (name.empty())
82 Throw<std::runtime_error>("nodestore: Missing path in NuDB backend");
83 }
84
86 size_t keyBytes,
87 Section const& keyValues,
90 nudb::context& context,
91 beast::Journal journal)
92 : j(journal)
95 , name(get(keyValues, Keys::kPath))
96 , blockSize(parseBlockSize(name, keyValues, journal))
97 , db(context)
98 , deletePath(false)
100 {
101 if (name.empty())
102 Throw<std::runtime_error>("nodestore: Missing path in NuDB backend");
103 }
104
105 ~NuDBBackend() override
106 {
107 try
108 {
109 // close can throw and we don't want the destructor to throw.
110 close();
111 }
112 catch (nudb::system_error const&) // NOLINT(bugprone-empty-catch)
113 {
114 // Don't allow exceptions to propagate out of destructors.
115 // close() has already logged the error.
116 }
117 }
118
120 getName() override
121 {
122 return name;
123 }
124
125 [[nodiscard]] std::optional<std::size_t>
126 getBlockSize() const override
127 {
128 return blockSize;
129 }
130
131 void
132 open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) override
133 {
134 using namespace boost::filesystem;
135 if (db.is_open())
136 {
137 // LCOV_EXCL_START
138 UNREACHABLE(
139 "xrpl::NodeStore::NuDBBackend::open : database is already "
140 "open");
141 JLOG(j.error()) << "database is already open";
142 return;
143 // LCOV_EXCL_STOP
144 }
145 auto const folder = path(name);
146 auto const dp = (folder / "nudb.dat").string();
147 auto const kp = (folder / "nudb.key").string();
148 auto const lp = (folder / "nudb.log").string();
149 nudb::error_code ec;
150 if (createIfMissing)
151 {
152 create_directories(folder);
153 nudb::create<nudb::xxhasher>(
154 dp, kp, lp, appType, uid, salt, keyBytes, blockSize, 0.50, ec);
155 if (ec == nudb::errc::file_exists)
156 ec = {};
157 if (ec)
159 }
160 db.open(dp, kp, lp, ec);
161 if (ec)
163
164 if (db.appnum() != kAppNum)
165 Throw<std::runtime_error>("nodestore: unknown appnum");
166 db.set_burst(burstSize);
167 }
168
169 bool
170 isOpen() override
171 {
172 return db.is_open();
173 }
174
175 void
176 open(bool createIfMissing) override
177 {
178 open(createIfMissing, kAppNum, nudb::make_uid(), nudb::make_salt());
179 }
180
181 void
182 close() override
183 {
184 if (db.is_open())
185 {
186 nudb::error_code ec;
187 db.close(ec);
188 if (ec)
189 {
190 // Log to make sure the nature of the error gets to the user.
191 JLOG(j.fatal()) << "NuBD close() failed: " << ec.message();
193 }
194
195 if (deletePath)
196 {
197 boost::filesystem::remove_all(name, ec);
198 if (ec)
199 {
200 JLOG(j.fatal())
201 << "Filesystem remove_all of " << name << " failed with: " << ec.message();
202 }
203 }
204 }
205 }
206
207 Status
208 fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pno) override
209 {
210 Status status = Status::Ok;
211 pno->reset();
212 nudb::error_code ec;
213 db.fetch(
214 hash.data(),
215 [&hash, pno, &status](void const* data, std::size_t size) {
216 nudb::detail::buffer bf;
217 auto const result = nodeobjectDecompress(data, size, bf);
218 DecodedBlob decoded(hash.data(), result.first, result.second);
219 if (!decoded.wasOk())
220 {
221 status = Status::DataCorrupt;
222 return;
223 }
224 *pno = decoded.createObject();
225 status = Status::Ok;
226 },
227 ec);
228 if (ec == nudb::error::key_not_found)
229 return Status::NotFound;
230 if (ec)
232 return status;
233 }
234
235 void
237 {
238 EncodedBlob const e(no);
239 nudb::error_code ec;
240 nudb::detail::buffer bf;
241 auto const result = nodeobjectCompress(e.getData(), e.getSize(), bf);
242 db.insert(e.getKey(), result.first, result.second, ec);
243 if (ec && ec != nudb::error::key_exists)
245 }
246
247 void
249 {
250 BatchWriteReport report{};
251 report.writeCount = 1;
252 auto const start = std::chrono::steady_clock::now();
253 doInsert(no);
256 scheduler.onBatchWrite(report);
257 }
258
259 void
260 storeBatch(Batch const& batch) override
261 {
262 BatchWriteReport report{};
263 report.writeCount = batch.size();
264 auto const start = std::chrono::steady_clock::now();
265 for (auto const& e : batch)
266 doInsert(e);
269 scheduler.onBatchWrite(report);
270 }
271
272 void
273 sync() override
274 {
275 }
276
277 void
279 {
280 auto const dp = db.dat_path();
281 auto const kp = db.key_path();
282 auto const lp = db.log_path();
283 // auto const appnum = db_.appnum();
284 nudb::error_code ec;
285 db.close(ec);
286 if (ec)
288 nudb::visit(
289 dp,
290 [&](void const* key,
292 void const* data,
293 std::size_t size,
294 nudb::error_code&) {
295 nudb::detail::buffer bf;
296 auto const result = nodeobjectDecompress(data, size, bf);
297 DecodedBlob decoded(key, result.first, result.second);
298 if (!decoded.wasOk())
299 {
300 ec = make_error_code(nudb::error::missing_value);
301 return;
302 }
303 f(decoded.createObject());
304 },
305 nudb::no_progress{},
306 ec);
307 if (ec)
309 db.open(dp, kp, lp, ec);
310 if (ec)
312 }
313
314 int
315 getWriteLoad() override
316 {
317 return 0;
318 }
319
320 void
321 setDeletePath() override
322 {
323 deletePath = true;
324 }
325
326 void
327 verify() override
328 {
329 auto const dp = db.dat_path();
330 auto const kp = db.key_path();
331 auto const lp = db.log_path();
332 nudb::error_code ec;
333 db.close(ec);
334 if (ec)
336 nudb::verify_info vi;
337 nudb::verify<nudb::xxhasher>(vi, dp, kp, 0, nudb::no_progress{}, ec);
338 if (ec)
340 db.open(dp, kp, lp, ec);
341 if (ec)
343 }
344
345 [[nodiscard]] int
346 fdRequired() const override
347 {
348 return 3;
349 }
350
351private:
352 static std::size_t
353 parseBlockSize(std::string const& name, Section const& keyValues, beast::Journal journal)
354 {
355 using namespace boost::filesystem;
356 auto const folder = path(name);
357 auto const kp = (folder / "nudb.key").string();
358
359 std::size_t const defaultSize = nudb::block_size(kp); // Default 4K from NuDB
360 std::size_t const blockSize = defaultSize;
361 std::string blockSizeStr;
362
363 if (!getIfExists(keyValues, Keys::kNudbBlockSize, blockSizeStr))
364 {
365 return blockSize; // Early return with default
366 }
367
368 try
369 {
370 std::size_t const parsedBlockSize = beast::lexicalCastThrow<std::size_t>(blockSizeStr);
371
372 // Validate: must be power of 2 between 4K and 32K
373 if (parsedBlockSize < 4096 || parsedBlockSize > 32768 ||
374 (parsedBlockSize & (parsedBlockSize - 1)) != 0)
375 {
377 s << "Invalid nudb_block_size: " << parsedBlockSize
378 << ". Must be power of 2 between 4096 and 32768.";
380 }
381
382 JLOG(journal.info()) << "Using custom NuDB block size: " << parsedBlockSize << " bytes";
383 return parsedBlockSize;
384 }
385 catch (std::exception const& e)
386 {
388 s << "Invalid nudb_block_size value: " << blockSizeStr << ". Error: " << e.what();
390 }
391 }
392};
393
394//------------------------------------------------------------------------------
395
396class NuDBFactory : public Factory
397{
398private:
400
401public:
402 explicit NuDBFactory(Manager& manager) : manager_(manager)
403 {
404 manager_.insert(*this);
405 }
406
407 [[nodiscard]] std::string
408 getName() const override
409 {
410 return "NuDB";
411 }
412
415 size_t keyBytes,
416 Section const& keyValues,
417 std::size_t burstSize,
418 Scheduler& scheduler,
419 beast::Journal journal) override
420 {
421 return std::make_unique<NuDBBackend>(keyBytes, keyValues, burstSize, scheduler, journal);
422 }
423
426 size_t keyBytes,
427 Section const& keyValues,
428 std::size_t burstSize,
429 Scheduler& scheduler,
430 nudb::context& context,
431 beast::Journal journal) override
432 {
434 keyBytes, keyValues, burstSize, scheduler, context, journal);
435 }
436};
437
438void
440{
441 static NuDBFactory const kInstance{manager};
442}
443
444} // namespace xrpl::NodeStore
A generic endpoint for log messages.
Definition Journal.h:38
Stream info() const
Definition Journal.h:303
pointer data()
Definition base_uint.h:106
A backend used for the NodeStore.
Definition Backend.h:19
Parsed key/value blob into NodeObject components.
Definition DecodedBlob.h:18
std::shared_ptr< NodeObject > createObject()
Create a NodeObject from this data.
bool wasOk() const noexcept
Determine if the decoding was successful.
Definition DecodedBlob.h:25
Convert a NodeObject from in-memory to database format.
Definition EncodedBlob.h:35
void const * getKey() const noexcept
Definition EncodedBlob.h:89
std::size_t getSize() const noexcept
Definition EncodedBlob.h:95
void const * getData() const noexcept
Base class for backend factories.
Definition Factory.h:17
Singleton for managing NodeStore factories and back ends.
Definition Manager.h:10
NuDBBackend(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)
void verify() override
Perform consistency checks on database.
Status fetch(uint256 const &hash, std::shared_ptr< NodeObject > *pno) override
Fetch a single object.
void storeBatch(Batch const &batch) override
Store a group of objects.
int getWriteLoad() override
Estimate the number of write operations pending.
std::optional< std::size_t > getBlockSize() const override
Get the block size for backends that support it.
static std::size_t parseBlockSize(std::string const &name, Section const &keyValues, beast::Journal journal)
std::size_t const blockSize
std::size_t const burstSize
int fdRequired() const override
Returns the number of file descriptors the backend expects to need.
void store(std::shared_ptr< NodeObject > const &no) override
Store a single object.
NuDBBackend(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, nudb::context &context, beast::Journal journal)
std::string getName() override
Get the human-readable name of this backend.
beast::Journal const j
void doInsert(std::shared_ptr< NodeObject > const &no)
void open(bool createIfMissing) override
Open the backend.
std::atomic< bool > deletePath
void open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) override
Open the backend.
static constexpr std::uint64_t kAppNum
void close() override
Close the backend.
void setDeletePath() override
Remove contents on disk upon destruction.
void forEach(std::function< void(std::shared_ptr< NodeObject >)> f) override
Visit every object in the database This is usually called during import.
bool isOpen() override
Returns true is the database is open.
std::unique_ptr< Backend > createInstance(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, nudb::context &context, beast::Journal journal) override
Create an instance of this factory's backend.
NuDBFactory(Manager &manager)
std::unique_ptr< Backend > createInstance(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal) override
Create an instance of this factory's backend.
std::string getName() const override
Retrieve the name of this factory.
Scheduling for asynchronous backend activity.
Holds a collection of configuration values.
Definition BasicConfig.h:24
T duration_cast(T... args)
T make_unique(T... args)
Out lexicalCastThrow(In in)
Convert from one type to another, throw on error.
void registerNuDBFactory(Manager &manager)
std::pair< void const *, std::size_t > nodeobjectCompress(void const *in, std::size_t inSize, BufferFactory &&bf)
Definition codec.h:197
Status
Return codes from Backend operations.
std::pair< void const *, std::size_t > nodeobjectDecompress(void const *in, std::size_t inSize, BufferFactory &&bf)
Definition codec.h:85
std::vector< std::shared_ptr< NodeObject > > Batch
A batch of NodeObjects to write at once.
std::error_code make_error_code(xrpl::TokenCodecErrc e)
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
bool getIfExists(Section const &section, std::string const &name, T &v)
BaseUInt< 256 > uint256
Definition base_uint.h:562
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
T size(T... args)
T str(T... args)
static constexpr auto kNudbBlockSize
Definition Constants.h:132
Contains information about a batch write operation.
T what(T... args)