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