rippled
Loading...
Searching...
No Matches
SHAMapStore_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/envconfig.h>
3
4#include <xrpld/app/main/Application.h>
5#include <xrpld/app/main/NodeStoreScheduler.h>
6#include <xrpld/app/misc/SHAMapStore.h>
7#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
8#include <xrpld/core/ConfigSections.h>
9
10#include <xrpl/nodestore/detail/DatabaseRotatingImp.h>
11#include <xrpl/protocol/jss.h>
12
13namespace xrpl {
14namespace test {
15
17{
18 static auto const deleteInterval = 8;
19
20 static auto
22 {
23 cfg->LEDGER_HISTORY = deleteInterval;
24 auto& section = cfg->section(ConfigSection::nodeDatabase());
25 section.set("online_delete", std::to_string(deleteInterval));
26 return cfg;
27 }
28
29 static auto
31 {
32 cfg = onlineDelete(std::move(cfg));
33 cfg->section(ConfigSection::nodeDatabase()).set("advisory_delete", "1");
34 return cfg;
35 }
36
37 bool
38 goodLedger(jtx::Env& env, Json::Value const& json, std::string ledgerID, bool checkDB = false)
39 {
40 auto good = json.isMember(jss::result) && !RPC::contains_error(json[jss::result]) &&
41 json[jss::result][jss::ledger][jss::ledger_index] == ledgerID;
42 if (!good || !checkDB)
43 return good;
44
45 auto const seq = json[jss::result][jss::ledger_index].asUInt();
46
48 if (!outInfo)
49 return false;
50 LedgerHeader const& info = outInfo.value();
51
52 std::string const outHash = to_string(info.hash);
53 LedgerIndex const outSeq = info.seq;
54 std::string const outParentHash = to_string(info.parentHash);
55 std::string const outDrops = to_string(info.drops);
56 std::uint64_t const outCloseTime = info.closeTime.time_since_epoch().count();
57 std::uint64_t const outParentCloseTime = info.parentCloseTime.time_since_epoch().count();
58 std::uint64_t const outCloseTimeResolution = info.closeTimeResolution.count();
59 std::uint64_t const outCloseFlags = info.closeFlags;
60 std::string const outAccountHash = to_string(info.accountHash);
61 std::string const outTxHash = to_string(info.txHash);
62
63 auto const& ledger = json[jss::result][jss::ledger];
64 return outHash == ledger[jss::ledger_hash].asString() && outSeq == seq &&
65 outParentHash == ledger[jss::parent_hash].asString() && outDrops == ledger[jss::total_coins].asString() &&
66 outCloseTime == ledger[jss::close_time].asUInt() &&
67 outParentCloseTime == ledger[jss::parent_close_time].asUInt() &&
68 outCloseTimeResolution == ledger[jss::close_time_resolution].asUInt() &&
69 outCloseFlags == ledger[jss::close_flags].asUInt() &&
70 outAccountHash == ledger[jss::account_hash].asString() &&
71 outTxHash == ledger[jss::transaction_hash].asString();
72 }
73
74 bool
76 {
77 return json.isMember(jss::result) && RPC::contains_error(json[jss::result]) &&
78 json[jss::result][jss::error_code] == error;
79 }
80
83 {
84 BEAST_EXPECT(
85 json.isMember(jss::result) && json[jss::result].isMember(jss::ledger) &&
86 json[jss::result][jss::ledger].isMember(jss::ledger_hash) &&
87 json[jss::result][jss::ledger][jss::ledger_hash].isString());
88 return json[jss::result][jss::ledger][jss::ledger_hash].asString();
89 }
90
91 void
92 ledgerCheck(jtx::Env& env, int const rows, int const first)
93 {
94 auto const [actualRows, actualFirst, actualLast] = env.app().getRelationalDatabase().getLedgerCountMinMax();
95
96 BEAST_EXPECT(actualRows == rows);
97 BEAST_EXPECT(actualFirst == first);
98 BEAST_EXPECT(actualLast == first + rows - 1);
99 }
100
101 void
102 transactionCheck(jtx::Env& env, int const rows)
103 {
104 BEAST_EXPECT(env.app().getRelationalDatabase().getTransactionCount() == rows);
105 }
106
107 void
108 accountTransactionCheck(jtx::Env& env, int const rows)
109 {
110 BEAST_EXPECT(env.app().getRelationalDatabase().getAccountTransactionCount() == rows);
111 }
112
113 int
115 {
116 using namespace std::chrono_literals;
117
118 auto& store = env.app().getSHAMapStore();
119
120 int ledgerSeq = 3;
121 store.rendezvous();
122 BEAST_EXPECT(!store.getLastRotated());
123
124 env.close();
125 store.rendezvous();
126
127 auto ledger = env.rpc("ledger", "validated");
128 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++)));
129
130 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
131 return ledgerSeq;
132 }
133
134public:
135 void
137 {
138 using namespace std::chrono_literals;
139
140 testcase("clearPrior");
141 using namespace jtx;
142
143 Env env(*this, envconfig(onlineDelete));
144
145 auto& store = env.app().getSHAMapStore();
146 env.fund(XRP(10000), noripple("alice"));
147
148 ledgerCheck(env, 1, 2);
149 transactionCheck(env, 0);
151
153
154 auto ledgerTmp = env.rpc("ledger", "0");
155 BEAST_EXPECT(bad(ledgerTmp));
156
157 ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1")));
158 BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
159
160 ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2")));
161 BEAST_EXPECT(goodLedger(env, ledgers[2], "2"));
162
163 ledgerTmp = env.rpc("ledger", "current");
164 BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
165
166 ledgerTmp = env.rpc("ledger", "4");
167 BEAST_EXPECT(bad(ledgerTmp));
168
169 ledgerTmp = env.rpc("ledger", "100");
170 BEAST_EXPECT(bad(ledgerTmp));
171
172 auto const firstSeq = waitForReady(env);
173 auto lastRotated = firstSeq - 1;
174
175 for (auto i = firstSeq + 1; i < deleteInterval + firstSeq; ++i)
176 {
177 env.fund(XRP(10000), noripple("test" + std::to_string(i)));
178 env.close();
179
180 ledgerTmp = env.rpc("ledger", "current");
181 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i)));
182 }
183 BEAST_EXPECT(store.getLastRotated() == lastRotated);
184
185 for (auto i = 3; i < deleteInterval + lastRotated; ++i)
186 {
187 ledgers.emplace(std::make_pair(i, env.rpc("ledger", std::to_string(i))));
188 BEAST_EXPECT(goodLedger(env, ledgers[i], std::to_string(i), true) && getHash(ledgers[i]).length());
189 }
190
191 ledgerCheck(env, deleteInterval + 1, 2);
194
195 {
196 // Closing one more ledger triggers a rotate
197 env.close();
198
199 auto ledger = env.rpc("ledger", "current");
200 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(deleteInterval + 4)));
201 }
202
203 store.rendezvous();
204
205 BEAST_EXPECT(store.getLastRotated() == deleteInterval + 3);
206 lastRotated = store.getLastRotated();
207 BEAST_EXPECT(lastRotated == 11);
208
209 // That took care of the fake hashes
210 ledgerCheck(env, deleteInterval + 1, 3);
213
214 // The last iteration of this loop should trigger a rotate
215 for (auto i = lastRotated - 1; i < lastRotated + deleteInterval - 1; ++i)
216 {
217 env.close();
218
219 ledgerTmp = env.rpc("ledger", "current");
220 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i + 3)));
221
222 ledgers.emplace(std::make_pair(i, env.rpc("ledger", std::to_string(i))));
223 BEAST_EXPECT(store.getLastRotated() == lastRotated || i == lastRotated + deleteInterval - 2);
224 BEAST_EXPECT(goodLedger(env, ledgers[i], std::to_string(i), true) && getHash(ledgers[i]).length());
225 }
226
227 store.rendezvous();
228
229 BEAST_EXPECT(store.getLastRotated() == deleteInterval + lastRotated);
230
231 ledgerCheck(env, deleteInterval + 1, lastRotated);
232 transactionCheck(env, 0);
234 }
235
236 void
238 {
239 testcase("automatic online_delete");
240 using namespace jtx;
241 using namespace std::chrono_literals;
242
243 Env env(*this, envconfig(onlineDelete));
244 auto& store = env.app().getSHAMapStore();
245
246 auto ledgerSeq = waitForReady(env);
247 auto lastRotated = ledgerSeq - 1;
248 BEAST_EXPECT(store.getLastRotated() == lastRotated);
249 BEAST_EXPECT(lastRotated != 2);
250
251 // Because advisory_delete is unset,
252 // "can_delete" is disabled.
253 auto const canDelete = env.rpc("can_delete");
254 BEAST_EXPECT(bad(canDelete, rpcNOT_ENABLED));
255
256 // Close ledgers without triggering a rotate
257 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
258 {
259 env.close();
260
261 auto ledger = env.rpc("ledger", "validated");
262 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
263 }
264
265 store.rendezvous();
266
267 // The database will always have back to ledger 2,
268 // regardless of lastRotated.
269 ledgerCheck(env, ledgerSeq - 2, 2);
270 BEAST_EXPECT(lastRotated == store.getLastRotated());
271
272 {
273 // Closing one more ledger triggers a rotate
274 env.close();
275
276 auto ledger = env.rpc("ledger", "validated");
277 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
278 }
279
280 store.rendezvous();
281
282 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
283 BEAST_EXPECT(lastRotated != store.getLastRotated());
284
285 lastRotated = store.getLastRotated();
286
287 // Close enough ledgers to trigger another rotate
288 for (; ledgerSeq < lastRotated + deleteInterval + 1; ++ledgerSeq)
289 {
290 env.close();
291
292 auto ledger = env.rpc("ledger", "validated");
293 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
294 }
295
296 store.rendezvous();
297
298 ledgerCheck(env, deleteInterval + 1, lastRotated);
299 BEAST_EXPECT(lastRotated != store.getLastRotated());
300 }
301
302 void
304 {
305 testcase("online_delete with advisory_delete");
306 using namespace jtx;
307 using namespace std::chrono_literals;
308
309 // Same config with advisory_delete enabled
310 Env env(*this, envconfig(advisoryDelete));
311 auto& store = env.app().getSHAMapStore();
312
313 auto ledgerSeq = waitForReady(env);
314 auto lastRotated = ledgerSeq - 1;
315 BEAST_EXPECT(store.getLastRotated() == lastRotated);
316 BEAST_EXPECT(lastRotated != 2);
317
318 auto canDelete = env.rpc("can_delete");
319 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
320 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
321
322 canDelete = env.rpc("can_delete", "never");
323 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
324 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
325
326 auto const firstBatch = deleteInterval + ledgerSeq;
327 for (; ledgerSeq < firstBatch; ++ledgerSeq)
328 {
329 env.close();
330
331 auto ledger = env.rpc("ledger", "validated");
332 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
333 }
334
335 store.rendezvous();
336
337 ledgerCheck(env, ledgerSeq - 2, 2);
338 BEAST_EXPECT(lastRotated == store.getLastRotated());
339
340 // This does not kick off a cleanup
341 canDelete = env.rpc("can_delete", std::to_string(ledgerSeq + deleteInterval / 2));
342 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
343 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq + deleteInterval / 2);
344
345 store.rendezvous();
346
347 ledgerCheck(env, ledgerSeq - 2, 2);
348 BEAST_EXPECT(store.getLastRotated() == lastRotated);
349
350 {
351 // This kicks off a cleanup, but it stays small.
352 env.close();
353
354 auto ledger = env.rpc("ledger", "validated");
355 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
356 }
357
358 store.rendezvous();
359
360 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
361
362 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
363 lastRotated = ledgerSeq - 1;
364
365 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
366 {
367 // No cleanups in this loop.
368 env.close();
369
370 auto ledger = env.rpc("ledger", "validated");
371 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
372 }
373
374 store.rendezvous();
375
376 BEAST_EXPECT(store.getLastRotated() == lastRotated);
377
378 {
379 // This kicks off another cleanup.
380 env.close();
381
382 auto ledger = env.rpc("ledger", "validated");
383 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
384 }
385
386 store.rendezvous();
387
388 ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
389
390 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
391 lastRotated = ledgerSeq - 1;
392
393 // This does not kick off a cleanup
394 canDelete = env.rpc("can_delete", "always");
395 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
396 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == std::numeric_limits<unsigned int>::max());
397
398 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
399 {
400 // No cleanups in this loop.
401 env.close();
402
403 auto ledger = env.rpc("ledger", "validated");
404 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
405 }
406
407 store.rendezvous();
408
409 BEAST_EXPECT(store.getLastRotated() == lastRotated);
410
411 {
412 // This kicks off another cleanup.
413 env.close();
414
415 auto ledger = env.rpc("ledger", "validated");
416 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
417 }
418
419 store.rendezvous();
420
421 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
422
423 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
424 lastRotated = ledgerSeq - 1;
425
426 // This does not kick off a cleanup
427 canDelete = env.rpc("can_delete", "now");
428 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
429 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
430
431 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
432 {
433 // No cleanups in this loop.
434 env.close();
435
436 auto ledger = env.rpc("ledger", "validated");
437 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
438 }
439
440 store.rendezvous();
441
442 BEAST_EXPECT(store.getLastRotated() == lastRotated);
443
444 {
445 // This kicks off another cleanup.
446 env.close();
447
448 auto ledger = env.rpc("ledger", "validated");
449 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
450 }
451
452 store.rendezvous();
453
454 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
455
456 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
457 lastRotated = ledgerSeq - 1;
458 }
459
462 {
464 boost::filesystem::path newPath;
465
466 if (!BEAST_EXPECT(path.size()))
467 return {};
468 newPath = path;
469 section.set("path", newPath.string());
470
472 section,
474 scheduler,
475 env.app().logs().journal("NodeStoreTest"))};
476 backend->open();
477 return backend;
478 }
479
480 void
482 {
483 // The only purpose of this test is to ensure that if something that
484 // should never happen happens, we don't get a deadlock.
485 testcase("rotate with lock contention");
486
487 using namespace jtx;
488 Env env(*this, envconfig(onlineDelete));
489
491 // Create NodeStore with two backends to allow online deletion of data.
492 // Normally, SHAMapStoreImp handles all these details.
493 NodeStoreScheduler scheduler(env.app().getJobQueue());
494
495 std::string const writableDb = "write";
496 std::string const archiveDb = "archive";
497 auto writableBackend = makeBackendRotating(env, scheduler, writableDb);
498 auto archiveBackend = makeBackendRotating(env, scheduler, archiveDb);
499
500 constexpr int readThreads = 4;
501 auto nscfg = env.app().config().section(ConfigSection::nodeDatabase());
503 scheduler,
504 readThreads,
505 std::move(writableBackend),
506 std::move(archiveBackend),
507 nscfg,
508 env.app().logs().journal("NodeStoreTest"));
509
511 // Check basic functionality
512 using namespace std::chrono_literals;
513 std::atomic<int> threadNum = 0;
514
515 {
516 auto newBackend = makeBackendRotating(env, scheduler, std::to_string(++threadNum));
517
518 auto const cb = [&](std::string const& writableName, std::string const& archiveName) {
519 BEAST_EXPECT(writableName == "1");
520 BEAST_EXPECT(archiveName == "write");
521 // Ensure that dbr functions can be called from within the
522 // callback
523 BEAST_EXPECT(dbr->getName() == "1");
524 };
525
526 dbr->rotate(std::move(newBackend), cb);
527 }
528 BEAST_EXPECT(threadNum == 1);
529 BEAST_EXPECT(dbr->getName() == "1");
530
532 // Do something stupid. Try to re-enter rotate from inside the callback.
533 {
534 auto const cb = [&](std::string const& writableName, std::string const& archiveName) {
535 BEAST_EXPECT(writableName == "3");
536 BEAST_EXPECT(archiveName == "2");
537 // Ensure that dbr functions can be called from within the
538 // callback
539 BEAST_EXPECT(dbr->getName() == "3");
540 };
541 auto const cbReentrant = [&](std::string const& writableName, std::string const& archiveName) {
542 BEAST_EXPECT(writableName == "2");
543 BEAST_EXPECT(archiveName == "1");
544 auto newBackend = makeBackendRotating(env, scheduler, std::to_string(++threadNum));
545 // Reminder: doing this is stupid and should never happen
546 dbr->rotate(std::move(newBackend), cb);
547 };
548 auto newBackend = makeBackendRotating(env, scheduler, std::to_string(++threadNum));
549 dbr->rotate(std::move(newBackend), cbReentrant);
550 }
551
552 BEAST_EXPECT(threadNum == 3);
553 BEAST_EXPECT(dbr->getName() == "3");
554 }
555
556 void
557 run() override
558 {
559 testClear();
562 testRotate();
563 }
564};
565
566// VFALCO This test fails because of thread asynchronous issues
567BEAST_DEFINE_TESTSUITE(SHAMapStore, app, xrpl);
568
569} // namespace test
570} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
virtual Config & config()=0
Section & section(std::string const &name)
Returns the section with the given name.
int getValueFor(SizedItem item, std::optional< std::size_t > node=std::nullopt) const
Retrieve the default value for the item at the specified node size.
Definition Config.cpp:1015
beast::Journal journal(std::string const &name)
Definition Log.cpp:134
A NodeStore::Scheduler which uses the JobQueue.
static Manager & instance()
Returns the instance of the manager singleton.
virtual std::unique_ptr< Backend > make_Backend(Section const &parameters, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)=0
Create a backend.
virtual std::size_t getAccountTransactionCount()=0
getAccountTransactionCount Returns the number of account transactions.
virtual struct CountMinMax getLedgerCountMinMax()=0
getLedgerCountMinMax Returns the minimum ledger sequence, maximum ledger sequence and total number of...
virtual std::size_t getTransactionCount()=0
getTransactionCount Returns the number of transactions.
virtual std::optional< LedgerHeader > getLedgerInfoByIndex(LedgerIndex ledgerSeq)=0
getLedgerInfoByIndex Returns a ledger by its sequence.
class to create database, launch online delete thread, and related SQLite database
Definition SHAMapStore.h:18
virtual void rendezvous() const =0
Holds a collection of configuration values.
Definition BasicConfig.h:24
virtual Logs & logs()=0
virtual JobQueue & getJobQueue()=0
virtual RelationalDatabase & getRelationalDatabase()=0
virtual SHAMapStore & getSHAMapStore()=0
static auto onlineDelete(std::unique_ptr< Config > cfg)
static auto advisoryDelete(std::unique_ptr< Config > cfg)
bool bad(Json::Value const &json, error_code_i error=rpcLGR_NOT_FOUND)
std::string getHash(Json::Value const &json)
std::unique_ptr< NodeStore::Backend > makeBackendRotating(jtx::Env &env, NodeStoreScheduler &scheduler, std::string path)
void ledgerCheck(jtx::Env &env, int const rows, int const first)
void accountTransactionCheck(jtx::Env &env, int const rows)
void run() override
Runs the suite.
bool goodLedger(jtx::Env &env, Json::Value const &json, std::string ledgerID, bool checkDB=false)
void transactionCheck(jtx::Env &env, int const rows)
A transaction testing environment.
Definition Env.h:119
Application & app()
Definition Env.h:251
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:98
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:261
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:792
Inject raw JSON.
Definition jtx_json.h:13
Add a path.
Definition paths.h:37
T emplace(T... args)
T is_same_v
T make_pair(T... args)
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:34
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
constexpr auto megabytes(T value) noexcept
error_code_i
Definition ErrorCodes.h:20
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:52
@ rpcNOT_ENABLED
Definition ErrorCodes.h:39
static std::string nodeDatabase()
Information about the notional ledger backing the view.
NetClock::time_point parentCloseTime
NetClock::duration closeTimeResolution
NetClock::time_point closeTime
Set the sequence number on a JTx.
Definition seq.h:14
T time_since_epoch(T... args)
T to_string(T... args)
T value(T... args)