xrpld
Loading...
Searching...
No Matches
Database_test.cpp
1#include <test/jtx/CheckMessageLogs.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/envconfig.h>
4#include <test/nodestore/TestBase.h>
5#include <test/unit_test/SuiteJournal.h>
6
7#include <xrpld/core/Config.h>
8
9#include <xrpl/basics/ByteUtilities.h>
10#include <xrpl/beast/unit_test/suite.h>
11#include <xrpl/beast/utility/Journal.h>
12#include <xrpl/beast/utility/temp_dir.h>
13#include <xrpl/beast/xor_shift_engine.h>
14#include <xrpl/config/BasicConfig.h>
15#include <xrpl/config/Constants.h>
16#include <xrpl/nodestore/Database.h>
17#include <xrpl/nodestore/DummyScheduler.h>
18#include <xrpl/nodestore/Manager.h>
19#include <xrpl/nodestore/Types.h>
20#include <xrpl/protocol/SystemParameters.h>
21#include <xrpl/rdb/DatabaseCon.h>
22
23#include <algorithm>
24#include <cstdint>
25#include <cstring>
26#include <memory>
27#include <stdexcept>
28#include <string>
29#include <utility>
30
31namespace xrpl::NodeStore {
32
33class Database_test : public TestBase
34{
36
37public:
38 Database_test() : journal_("Database_test", *this)
39 {
40 }
41
42 void
44 {
45 testcase("Config");
46
47 using namespace xrpl::test;
48 using namespace xrpl::test::jtx;
49
50 auto const integrityWarning =
51 "reducing the data integrity guarantees from the "
52 "default [sqlite] behavior is not recommended for "
53 "nodes storing large amounts of history, because of the "
54 "difficulty inherent in rebuilding corrupted data.";
55 {
56 // defaults
57 Env env(*this);
58
59 auto const s = setupDatabaseCon(env.app().config());
60
61 if (BEAST_EXPECT(s.globalPragma->size() == 3))
62 {
63 BEAST_EXPECT(s.globalPragma->at(0) == "PRAGMA journal_mode=wal;");
64 BEAST_EXPECT(s.globalPragma->at(1) == "PRAGMA synchronous=normal;");
65 BEAST_EXPECT(s.globalPragma->at(2) == "PRAGMA temp_store=file;");
66 }
67 }
68 {
69 // High safety level
71
72 bool found = false;
73 Env env = [&]() {
74 auto p = test::jtx::envconfig();
75 {
76 auto& section = p->section(Sections::kSqlite);
77 section.set(Keys::kSafetyLevel, "high");
78 }
79 p->ledgerHistory = 100'000'000;
80
81 return Env(
82 *this,
83 std::move(p),
84 std::make_unique<CheckMessageLogs>(integrityWarning, &found),
86 }();
87
88 BEAST_EXPECT(!found);
89 auto const s = setupDatabaseCon(env.app().config());
90 if (BEAST_EXPECT(s.globalPragma->size() == 3))
91 {
92 BEAST_EXPECT(s.globalPragma->at(0) == "PRAGMA journal_mode=wal;");
93 BEAST_EXPECT(s.globalPragma->at(1) == "PRAGMA synchronous=normal;");
94 BEAST_EXPECT(s.globalPragma->at(2) == "PRAGMA temp_store=file;");
95 }
96 }
97 {
98 // Low safety level
100
101 bool found = false;
102 Env env = [&]() {
103 auto p = test::jtx::envconfig();
104 {
105 auto& section = p->section(Sections::kSqlite);
106 section.set(Keys::kSafetyLevel, "low");
107 }
108 p->ledgerHistory = 100'000'000;
109
110 return Env(
111 *this,
112 std::move(p),
113 std::make_unique<CheckMessageLogs>(integrityWarning, &found),
115 }();
116
117 BEAST_EXPECT(found);
118 auto const s = setupDatabaseCon(env.app().config());
119 if (BEAST_EXPECT(s.globalPragma->size() == 3))
120 {
121 BEAST_EXPECT(s.globalPragma->at(0) == "PRAGMA journal_mode=memory;");
122 BEAST_EXPECT(s.globalPragma->at(1) == "PRAGMA synchronous=off;");
123 BEAST_EXPECT(s.globalPragma->at(2) == "PRAGMA temp_store=memory;");
124 }
125 }
126 {
127 // Override individual settings
129
130 bool found = false;
131 Env env = [&]() {
132 auto p = test::jtx::envconfig();
133 {
134 auto& section = p->section(Sections::kSqlite);
135 section.set(Keys::kJournalMode, "off");
136 section.set(Keys::kSynchronous, "extra");
137 section.set(Keys::kTempStore, "default");
138 }
139
140 return Env(
141 *this,
142 std::move(p),
143 std::make_unique<CheckMessageLogs>(integrityWarning, &found),
145 }();
146
147 // No warning, even though higher risk settings were used because
148 // LEDGER_HISTORY is small
149 BEAST_EXPECT(!found);
150 auto const s = setupDatabaseCon(env.app().config());
151 if (BEAST_EXPECT(s.globalPragma->size() == 3))
152 {
153 BEAST_EXPECT(s.globalPragma->at(0) == "PRAGMA journal_mode=off;");
154 BEAST_EXPECT(s.globalPragma->at(1) == "PRAGMA synchronous=extra;");
155 BEAST_EXPECT(s.globalPragma->at(2) == "PRAGMA temp_store=default;");
156 }
157 }
158 {
159 // Override individual settings with large history
161
162 bool found = false;
163 Env env = [&]() {
164 auto p = test::jtx::envconfig();
165 {
166 auto& section = p->section(Sections::kSqlite);
167 section.set(Keys::kJournalMode, "off");
168 section.set(Keys::kSynchronous, "extra");
169 section.set(Keys::kTempStore, "default");
170 }
171 p->ledgerHistory = 50'000'000;
172
173 return Env(
174 *this,
175 std::move(p),
176 std::make_unique<CheckMessageLogs>(integrityWarning, &found),
178 }();
179
180 // No warning, even though higher risk settings were used because
181 // LEDGER_HISTORY is small
182 BEAST_EXPECT(found);
183 auto const s = setupDatabaseCon(env.app().config());
184 if (BEAST_EXPECT(s.globalPragma->size() == 3))
185 {
186 BEAST_EXPECT(s.globalPragma->at(0) == "PRAGMA journal_mode=off;");
187 BEAST_EXPECT(s.globalPragma->at(1) == "PRAGMA synchronous=extra;");
188 BEAST_EXPECT(s.globalPragma->at(2) == "PRAGMA temp_store=default;");
189 }
190 }
191 {
192 // Error: Mix safety_level and individual settings
194 auto const expected =
195 "Failed to initialize SQL databases: "
196 "Configuration file may not define both \"safety_level\" and "
197 "\"journal_mode\"";
198 bool found = false;
199
200 auto p = test::jtx::envconfig();
201 {
202 auto& section = p->section(Sections::kSqlite);
203 section.set(Keys::kSafetyLevel, "low");
204 section.set(Keys::kJournalMode, "off");
205 section.set(Keys::kSynchronous, "extra");
206 section.set(Keys::kTempStore, "default");
207 }
208
209 try
210 {
211 Env const env(
212 *this,
213 std::move(p),
214 std::make_unique<CheckMessageLogs>(expected, &found),
216 fail();
217 }
218 catch (...)
219 {
220 BEAST_EXPECT(found);
221 }
222 }
223 {
224 // Error: Mix safety_level and one setting (gotta catch 'em all)
226 auto const expected =
227 "Failed to initialize SQL databases: Configuration file may "
228 "not define both \"safety_level\" and \"journal_mode\"";
229 bool found = false;
230
231 auto p = test::jtx::envconfig();
232 {
233 auto& section = p->section(Sections::kSqlite);
234 section.set(Keys::kSafetyLevel, "high");
235 section.set(Keys::kJournalMode, "off");
236 }
237
238 try
239 {
240 Env const env(
241 *this,
242 std::move(p),
243 std::make_unique<CheckMessageLogs>(expected, &found),
245 fail();
246 }
247 catch (...)
248 {
249 BEAST_EXPECT(found);
250 }
251 }
252 {
253 // Error: Mix safety_level and one setting (gotta catch 'em all)
255 auto const expected =
256 "Failed to initialize SQL databases: Configuration file may "
257 "not define both \"safety_level\" and \"synchronous\"";
258 bool found = false;
259
260 auto p = test::jtx::envconfig();
261 {
262 auto& section = p->section(Sections::kSqlite);
263 section.set(Keys::kSafetyLevel, "low");
264 section.set(Keys::kSynchronous, "extra");
265 }
266
267 try
268 {
269 Env const env(
270 *this,
271 std::move(p),
272 std::make_unique<CheckMessageLogs>(expected, &found),
274 fail();
275 }
276 catch (...)
277 {
278 BEAST_EXPECT(found);
279 }
280 }
281 {
282 // Error: Mix safety_level and one setting (gotta catch 'em all)
284 auto const expected =
285 "Failed to initialize SQL databases: Configuration file may "
286 "not define both \"safety_level\" and \"temp_store\"";
287 bool found = false;
288
289 auto p = test::jtx::envconfig();
290 {
291 auto& section = p->section(Sections::kSqlite);
292 section.set(Keys::kSafetyLevel, "high");
293 section.set(Keys::kTempStore, "default");
294 }
295
296 try
297 {
298 Env const env(
299 *this,
300 std::move(p),
301 std::make_unique<CheckMessageLogs>(expected, &found),
303 fail();
304 }
305 catch (...)
306 {
307 BEAST_EXPECT(found);
308 }
309 }
310 {
311 // Error: Invalid value
313 auto const expected =
314 "Failed to initialize SQL databases: Invalid safety_level "
315 "value: slow";
316 bool found = false;
317
318 auto p = test::jtx::envconfig();
319 {
320 auto& section = p->section(Sections::kSqlite);
321 section.set(Keys::kSafetyLevel, "slow");
322 }
323
324 try
325 {
326 Env const env(
327 *this,
328 std::move(p),
329 std::make_unique<CheckMessageLogs>(expected, &found),
331 fail();
332 }
333 catch (...)
334 {
335 BEAST_EXPECT(found);
336 }
337 }
338 {
339 // Error: Invalid value
341 auto const expected =
342 "Failed to initialize SQL databases: Invalid journal_mode "
343 "value: fast";
344 bool found = false;
345
346 auto p = test::jtx::envconfig();
347 {
348 auto& section = p->section(Sections::kSqlite);
349 section.set(Keys::kJournalMode, "fast");
350 }
351
352 try
353 {
354 Env const env(
355 *this,
356 std::move(p),
357 std::make_unique<CheckMessageLogs>(expected, &found),
359 fail();
360 }
361 catch (...)
362 {
363 BEAST_EXPECT(found);
364 }
365 }
366 {
367 // Error: Invalid value
369 auto const expected =
370 "Failed to initialize SQL databases: Invalid synchronous "
371 "value: instant";
372 bool found = false;
373
374 auto p = test::jtx::envconfig();
375 {
376 auto& section = p->section(Sections::kSqlite);
377 section.set(Keys::kSynchronous, "instant");
378 }
379
380 try
381 {
382 Env const env(
383 *this,
384 std::move(p),
385 std::make_unique<CheckMessageLogs>(expected, &found),
387 fail();
388 }
389 catch (...)
390 {
391 BEAST_EXPECT(found);
392 }
393 }
394 {
395 // Error: Invalid value
397 auto const expected =
398 "Failed to initialize SQL databases: Invalid temp_store "
399 "value: network";
400 bool found = false;
401
402 auto p = test::jtx::envconfig();
403 {
404 auto& section = p->section(Sections::kSqlite);
405 section.set(Keys::kTempStore, "network");
406 }
407
408 try
409 {
410 Env const env(
411 *this,
412 std::move(p),
413 std::make_unique<CheckMessageLogs>(expected, &found),
415 fail();
416 }
417 catch (...)
418 {
419 BEAST_EXPECT(found);
420 }
421 }
422 {
423 // N/A: Default values
424 Env env(*this);
425 auto const s = setupDatabaseCon(env.app().config());
426 if (BEAST_EXPECT(s.txPragma.size() == 4))
427 {
428 BEAST_EXPECT(s.txPragma.at(0) == "PRAGMA page_size=4096;");
429 BEAST_EXPECT(s.txPragma.at(1) == "PRAGMA journal_size_limit=1582080;");
430 BEAST_EXPECT(s.txPragma.at(2) == "PRAGMA max_page_count=4294967294;");
431 BEAST_EXPECT(s.txPragma.at(3) == "PRAGMA mmap_size=17179869184;");
432 }
433 }
434 {
435 // Success: Valid values
436 Env env = [&]() {
437 auto p = test::jtx::envconfig();
438 {
439 auto& section = p->section(Sections::kSqlite);
440 section.set(Keys::kPageSize, "512");
441 section.set(Keys::kJournalSizeLimit, "2582080");
442 }
443 return Env(*this, std::move(p));
444 }();
445 auto const s = setupDatabaseCon(env.app().config());
446 if (BEAST_EXPECT(s.txPragma.size() == 4))
447 {
448 BEAST_EXPECT(s.txPragma.at(0) == "PRAGMA page_size=512;");
449 BEAST_EXPECT(s.txPragma.at(1) == "PRAGMA journal_size_limit=2582080;");
450 BEAST_EXPECT(s.txPragma.at(2) == "PRAGMA max_page_count=4294967294;");
451 BEAST_EXPECT(s.txPragma.at(3) == "PRAGMA mmap_size=17179869184;");
452 }
453 }
454 {
455 // Error: Invalid values
456 auto const expected = "Invalid page_size. Must be between 512 and 65536.";
457 bool found = false;
458 auto p = test::jtx::envconfig();
459 {
460 auto& section = p->section(Sections::kSqlite);
461 section.set(Keys::kPageSize, "256");
462 }
463 try
464 {
465 Env const env(
466 *this,
467 std::move(p),
468 std::make_unique<CheckMessageLogs>(expected, &found),
470 fail();
471 }
472 catch (...)
473 {
474 BEAST_EXPECT(found);
475 }
476 }
477 {
478 // Error: Invalid values
479 auto const expected = "Invalid page_size. Must be between 512 and 65536.";
480 bool found = false;
481 auto p = test::jtx::envconfig();
482 {
483 auto& section = p->section(Sections::kSqlite);
484 section.set(Keys::kPageSize, "131072");
485 }
486 try
487 {
488 Env const env(
489 *this,
490 std::move(p),
491 std::make_unique<CheckMessageLogs>(expected, &found),
493 fail();
494 }
495 catch (...)
496 {
497 BEAST_EXPECT(found);
498 }
499 }
500 {
501 // Error: Invalid values
502 auto const expected = "Invalid page_size. Must be a power of 2.";
503 bool found = false;
504 auto p = test::jtx::envconfig();
505 {
506 auto& section = p->section(Sections::kSqlite);
507 section.set(Keys::kPageSize, "513");
508 }
509 try
510 {
511 Env const env(
512 *this,
513 std::move(p),
514 std::make_unique<CheckMessageLogs>(expected, &found),
516 fail();
517 }
518 catch (...)
519 {
520 BEAST_EXPECT(found);
521 }
522 }
523 }
524
525 //--------------------------------------------------------------------------
526
527 void
529 std::string const& destBackendType,
530 std::string const& srcBackendType,
531 std::int64_t seedValue)
532 {
533 DummyScheduler scheduler;
534
535 beast::TempDir const nodeDb;
536 Section srcParams;
537 srcParams.set(Keys::kType, srcBackendType);
538 srcParams.set(Keys::kPath, nodeDb.path());
539
540 // Create a batch
542
543 // Write to source db
544 {
546 Manager::instance().makeDatabase(megabytes(4), scheduler, 2, srcParams, journal_);
547 storeBatch(*src, batch);
548 }
549
550 Batch copy;
551
552 {
553 // Re-open the db
555 Manager::instance().makeDatabase(megabytes(4), scheduler, 2, srcParams, journal_);
556
557 // Set up the destination database
558 beast::TempDir const destDb;
559 Section destParams;
560 destParams.set(Keys::kType, destBackendType);
561 destParams.set(Keys::kPath, destDb.path());
562
564 Manager::instance().makeDatabase(megabytes(4), scheduler, 2, destParams, journal_);
565
566 testcase("import into '" + destBackendType + "' from '" + srcBackendType + "'");
567
568 // Do the import
569 dest->importDatabase(*src);
570
571 // Get the results of the import
572 fetchCopyOfBatch(*dest, &copy, batch);
573 }
574
575 // Canonicalize the source and destination batches
578 BEAST_EXPECT(areBatchesEqual(batch, copy));
579 }
580
581 //--------------------------------------------------------------------------
582
583 void
585 std::string const& type,
586 bool const testPersistence,
587 std::int64_t const seedValue,
588 int numObjsToTest = 2000)
589 {
590 DummyScheduler scheduler;
591
592 std::string const s = "NodeStore backend '" + type + "'";
593
594 testcase(s);
595
596 beast::TempDir const nodeDb;
597 Section nodeParams;
598 nodeParams.set(Keys::kType, type);
599 nodeParams.set(Keys::kPath, nodeDb.path());
600
601 beast::xor_shift_engine rng(seedValue);
602
603 // Create a batch
604 auto batch = createPredictableBatch(numObjsToTest, rng());
605
606 {
607 // Open the database
609 Manager::instance().makeDatabase(megabytes(4), scheduler, 2, nodeParams, journal_);
610
611 // Write the batch
612 storeBatch(*db, batch);
613
614 {
615 // Read it back in
616 Batch copy;
617 fetchCopyOfBatch(*db, &copy, batch);
618 BEAST_EXPECT(areBatchesEqual(batch, copy));
619 }
620
621 {
622 // Reorder and read the copy again
623 std::shuffle(batch.begin(), batch.end(), rng);
624 Batch copy;
625 fetchCopyOfBatch(*db, &copy, batch);
626 BEAST_EXPECT(areBatchesEqual(batch, copy));
627 }
628 }
629
630 if (testPersistence)
631 {
632 // Re-open the database without the ephemeral DB
634 Manager::instance().makeDatabase(megabytes(4), scheduler, 2, nodeParams, journal_);
635
636 // Read it back in
637 Batch copy;
638 fetchCopyOfBatch(*db, &copy, batch);
639
640 // Canonicalize the source and destination batches
643 BEAST_EXPECT(areBatchesEqual(batch, copy));
644 }
645
646 if (type == "memory")
647 {
648 // Verify default earliest ledger sequence
649 {
651 megabytes(4), scheduler, 2, nodeParams, journal_);
652 BEAST_EXPECT(db->earliestLedgerSeq() == kXrpLedgerEarliestSeq);
653 }
654
655 // Set an invalid earliest ledger sequence
656 try
657 {
658 nodeParams.set(Keys::kEarliestSeq, "0");
660 megabytes(4), scheduler, 2, nodeParams, journal_);
661 }
662 catch (std::runtime_error const& e)
663 {
664 BEAST_EXPECT(std::strcmp(e.what(), "Invalid earliest_seq") == 0);
665 }
666
667 {
668 // Set a valid earliest ledger sequence
669 nodeParams.set(Keys::kEarliestSeq, "1");
671 megabytes(4), scheduler, 2, nodeParams, journal_);
672
673 // Verify database uses the earliest ledger sequence setting
674 BEAST_EXPECT(db->earliestLedgerSeq() == 1);
675 }
676
677 // Create another database that attempts to set the value again
678 try
679 {
680 // Set to default earliest ledger sequence
683 megabytes(4), scheduler, 2, nodeParams, journal_);
684 }
685 catch (std::runtime_error const& e)
686 {
687 BEAST_EXPECT(std::strcmp(e.what(), "earliest_seq set more than once") == 0);
688 }
689 }
690 }
691
692 //--------------------------------------------------------------------------
693
694 void
695 run() override
696 {
697 std::int64_t const seedValue = 50;
698
699 testConfig();
700
701 testNodeStore("memory", false, seedValue);
702
703 // Persistent backend tests
704 {
705 testNodeStore("nudb", true, seedValue);
706
707#if XRPL_ROCKSDB_AVAILABLE
708 testNodeStore("rocksdb", true, seedValue);
709#endif
710 }
711
712 // Import tests
713 {
714 testImport("nudb", "nudb", seedValue);
715
716#if XRPL_ROCKSDB_AVAILABLE
717 testImport("rocksdb", "rocksdb", seedValue);
718#endif
719
720#if XRPL_ENABLE_SQLITE_BACKEND_TESTS
721 testImport("sqlite", "sqlite", seedValue);
722#endif
723 }
724 }
725};
726
728
729} // namespace xrpl::NodeStore
RAII temporary directory.
Definition temp_dir.h:15
std::string path() const
Get the native path for the temporary directory.
Definition temp_dir.h:47
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:522
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
virtual Config & config()=0
void run() override
Runs the suite.
void testImport(std::string const &destBackendType, std::string const &srcBackendType, std::int64_t seedValue)
void testNodeStore(std::string const &type, bool const testPersistence, std::int64_t const seedValue, int numObjsToTest=2000)
Persistency layer for NodeObject.
Definition Database.h:32
Simple NodeStore Scheduler that just performs the tasks synchronously.
static Manager & instance()
Returns the instance of the manager singleton.
virtual std::unique_ptr< Database > makeDatabase(std::size_t burstSize, Scheduler &scheduler, int readThreads, Section const &backendParameters, beast::Journal journal)=0
Construct a NodeStore database.
static bool areBatchesEqual(Batch const &lhs, Batch const &rhs)
Definition TestBase.h:95
void fetchCopyOfBatch(Backend &backend, Batch *pCopy, Batch const &batch)
Definition TestBase.h:130
static void storeBatch(Backend &backend, Batch const &batch)
Definition TestBase.h:120
static Batch createPredictableBatch(int numObjects, std::uint64_t seed)
Definition TestBase.h:57
static int const kNumObjectsToTest
Definition TestBase.h:52
Holds a collection of configuration values.
Definition BasicConfig.h:24
void set(std::string const &key, std::string const &value)
Set a key/value pair.
A transaction testing environment.
Definition Env.h:143
Application & app()
Definition Env.h:280
T make_unique(T... args)
detail::XorShiftEngine<> xor_shift_engine
XOR-shift Generator.
BEAST_DEFINE_TESTSUITE(Backend, nodestore, xrpl)
std::vector< std::shared_ptr< NodeObject > > Batch
A batch of NodeObjects to write at once.
Helpers for constructing Batch test transactions.
Definition batch.h:18
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:28
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr auto megabytes(T value) noexcept
DatabaseCon::Setup setupDatabaseCon(Config const &c, std::optional< beast::Journal > j=std::nullopt)
Definition Config.cpp:1219
static constexpr std::uint32_t kXrpLedgerEarliestSeq
The XRP ledger network's earliest allowed sequence.
T shuffle(T... args)
T sort(T... args)
T strcmp(T... args)
static std::unique_ptr< std::vector< std::string > const > globalPragma
Definition DatabaseCon.h:90
static constexpr auto kPageSize
Definition Constants.h:138
static constexpr auto kSafetyLevel
Definition Constants.h:150
static constexpr auto kTempStore
Definition Constants.h:165
static constexpr auto kJournalMode
Definition Constants.h:114
static constexpr auto kSynchronous
Definition Constants.h:163
static constexpr auto kJournalSizeLimit
Definition Constants.h:115
static constexpr auto kType
Definition Constants.h:170
static constexpr auto kPath
Definition Constants.h:140
static constexpr auto kEarliestSeq
Definition Constants.h:104
Binary function that satisfies the strict-weak-ordering requirement.
Definition TestBase.h:26
static constexpr auto kSqlite
Definition Constants.h:60
T to_string(T... args)
T what(T... args)