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 static 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
49 if (!outInfo)
50 return false;
51 LedgerHeader const& info = outInfo.value();
52
53 std::string const outHash = to_string(info.hash);
54 LedgerIndex const outSeq = info.seq;
55 std::string const outParentHash = to_string(info.parentHash);
56 std::string const outDrops = to_string(info.drops);
57 std::uint64_t const outCloseTime = info.closeTime.time_since_epoch().count();
58 std::uint64_t const outParentCloseTime = info.parentCloseTime.time_since_epoch().count();
59 std::uint64_t const outCloseTimeResolution = info.closeTimeResolution.count();
60 std::uint64_t const outCloseFlags = info.closeFlags;
61 std::string const outAccountHash = to_string(info.accountHash);
62 std::string const outTxHash = to_string(info.txHash);
63
64 auto const& ledger = json[jss::result][jss::ledger];
65 return outHash == ledger[jss::ledger_hash].asString() && outSeq == seq &&
66 outParentHash == ledger[jss::parent_hash].asString() &&
67 outDrops == ledger[jss::total_coins].asString() &&
68 outCloseTime == ledger[jss::close_time].asUInt() &&
69 outParentCloseTime == ledger[jss::parent_close_time].asUInt() &&
70 outCloseTimeResolution == ledger[jss::close_time_resolution].asUInt() &&
71 outCloseFlags == ledger[jss::close_flags].asUInt() &&
72 outAccountHash == ledger[jss::account_hash].asString() &&
73 outTxHash == ledger[jss::transaction_hash].asString();
74 }
75
76 static bool
78 {
79 return json.isMember(jss::result) && RPC::contains_error(json[jss::result]) &&
80 json[jss::result][jss::error_code] == error;
81 }
82
85 {
86 BEAST_EXPECT(
87 json.isMember(jss::result) && json[jss::result].isMember(jss::ledger) &&
88 json[jss::result][jss::ledger].isMember(jss::ledger_hash) &&
89 json[jss::result][jss::ledger][jss::ledger_hash].isString());
90 return json[jss::result][jss::ledger][jss::ledger_hash].asString();
91 }
92
93 void
94 ledgerCheck(jtx::Env& env, int const rows, int const first)
95 {
96 auto const [actualRows, actualFirst, actualLast] =
98
99 BEAST_EXPECT(actualRows == rows);
100 BEAST_EXPECT(actualFirst == first);
101 BEAST_EXPECT(actualLast == first + rows - 1);
102 }
103
104 void
105 transactionCheck(jtx::Env& env, int const rows)
106 {
107 BEAST_EXPECT(env.app().getRelationalDatabase().getTransactionCount() == rows);
108 }
109
110 void
111 accountTransactionCheck(jtx::Env& env, int const rows)
112 {
113 BEAST_EXPECT(env.app().getRelationalDatabase().getAccountTransactionCount() == rows);
114 }
115
116 int
118 {
119 using namespace std::chrono_literals;
120
121 auto& store = env.app().getSHAMapStore();
122
123 int ledgerSeq = 3;
124 store.rendezvous();
125 BEAST_EXPECT(!store.getLastRotated());
126
127 env.close();
128 store.rendezvous();
129
130 auto ledger = env.rpc("ledger", "validated");
131 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++)));
132
133 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
134 return ledgerSeq;
135 }
136
137public:
138 void
140 {
141 using namespace std::chrono_literals;
142
143 testcase("clearPrior");
144 using namespace jtx;
145
146 Env env(*this, envconfig(onlineDelete));
147
148 auto& store = env.app().getSHAMapStore();
149 env.fund(XRP(10000), noripple("alice"));
150
151 ledgerCheck(env, 1, 2);
152 transactionCheck(env, 0);
154
156
157 auto ledgerTmp = env.rpc("ledger", "0");
158 BEAST_EXPECT(bad(ledgerTmp));
159
160 ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1")));
161 BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
162
163 ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2")));
164 BEAST_EXPECT(goodLedger(env, ledgers[2], "2"));
165
166 ledgerTmp = env.rpc("ledger", "current");
167 BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
168
169 ledgerTmp = env.rpc("ledger", "4");
170 BEAST_EXPECT(bad(ledgerTmp));
171
172 ledgerTmp = env.rpc("ledger", "100");
173 BEAST_EXPECT(bad(ledgerTmp));
174
175 auto const firstSeq = waitForReady(env);
176 auto lastRotated = firstSeq - 1;
177
178 for (auto i = firstSeq + 1; i < deleteInterval + firstSeq; ++i)
179 {
180 env.fund(XRP(10000), noripple("test" + std::to_string(i)));
181 env.close();
182
183 ledgerTmp = env.rpc("ledger", "current");
184 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i)));
185 }
186 BEAST_EXPECT(store.getLastRotated() == lastRotated);
187
188 for (auto i = 3; i < deleteInterval + lastRotated; ++i)
189 {
190 ledgers.emplace(std::make_pair(i, env.rpc("ledger", std::to_string(i))));
191 BEAST_EXPECT(
192 goodLedger(env, ledgers[i], std::to_string(i), true) &&
193 !getHash(ledgers[i]).empty());
194 }
195
196 ledgerCheck(env, deleteInterval + 1, 2);
199
200 {
201 // Closing one more ledger triggers a rotate
202 env.close();
203
204 auto ledger = env.rpc("ledger", "current");
205 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(deleteInterval + 4)));
206 }
207
208 store.rendezvous();
209
210 BEAST_EXPECT(store.getLastRotated() == deleteInterval + 3);
211 lastRotated = store.getLastRotated();
212 BEAST_EXPECT(lastRotated == 11);
213
214 // That took care of the fake hashes
215 ledgerCheck(env, deleteInterval + 1, 3);
218
219 // The last iteration of this loop should trigger a rotate
220 for (auto i = lastRotated - 1; i < lastRotated + deleteInterval - 1; ++i)
221 {
222 env.close();
223
224 ledgerTmp = env.rpc("ledger", "current");
225 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i + 3)));
226
227 ledgers.emplace(std::make_pair(i, env.rpc("ledger", std::to_string(i))));
228 BEAST_EXPECT(
229 store.getLastRotated() == lastRotated || i == lastRotated + deleteInterval - 2);
230 BEAST_EXPECT(
231 goodLedger(env, ledgers[i], std::to_string(i), true) &&
232 !getHash(ledgers[i]).empty());
233 }
234
235 store.rendezvous();
236
237 BEAST_EXPECT(store.getLastRotated() == deleteInterval + lastRotated);
238
239 ledgerCheck(env, deleteInterval + 1, lastRotated);
240 transactionCheck(env, 0);
242 }
243
244 void
246 {
247 testcase("automatic online_delete");
248 using namespace jtx;
249 using namespace std::chrono_literals;
250
251 Env env(*this, envconfig(onlineDelete));
252 auto& store = env.app().getSHAMapStore();
253
254 auto ledgerSeq = waitForReady(env);
255 auto lastRotated = ledgerSeq - 1;
256 BEAST_EXPECT(store.getLastRotated() == lastRotated);
257 BEAST_EXPECT(lastRotated != 2);
258
259 // Because advisory_delete is unset,
260 // "can_delete" is disabled.
261 auto const canDelete = env.rpc("can_delete");
262 BEAST_EXPECT(bad(canDelete, rpcNOT_ENABLED));
263
264 // Close ledgers without triggering a rotate
265 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
266 {
267 env.close();
268
269 auto ledger = env.rpc("ledger", "validated");
270 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
271 }
272
273 store.rendezvous();
274
275 // The database will always have back to ledger 2,
276 // regardless of lastRotated.
277 ledgerCheck(env, ledgerSeq - 2, 2);
278 BEAST_EXPECT(lastRotated == store.getLastRotated());
279
280 {
281 // Closing one more ledger triggers a rotate
282 env.close();
283
284 auto ledger = env.rpc("ledger", "validated");
285 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
286 }
287
288 store.rendezvous();
289
290 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
291 BEAST_EXPECT(lastRotated != store.getLastRotated());
292
293 lastRotated = store.getLastRotated();
294
295 // Close enough ledgers to trigger another rotate
296 for (; ledgerSeq < lastRotated + deleteInterval + 1; ++ledgerSeq)
297 {
298 env.close();
299
300 auto ledger = env.rpc("ledger", "validated");
301 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
302 }
303
304 store.rendezvous();
305
306 ledgerCheck(env, deleteInterval + 1, lastRotated);
307 BEAST_EXPECT(lastRotated != store.getLastRotated());
308 }
309
310 void
312 {
313 testcase("online_delete with advisory_delete");
314 using namespace jtx;
315 using namespace std::chrono_literals;
316
317 // Same config with advisory_delete enabled
318 Env env(*this, envconfig(advisoryDelete));
319 auto& store = env.app().getSHAMapStore();
320
321 auto ledgerSeq = waitForReady(env);
322 auto lastRotated = ledgerSeq - 1;
323 BEAST_EXPECT(store.getLastRotated() == lastRotated);
324 BEAST_EXPECT(lastRotated != 2);
325
326 auto canDelete = env.rpc("can_delete");
327 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
328 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
329
330 canDelete = env.rpc("can_delete", "never");
331 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
332 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
333
334 auto const firstBatch = deleteInterval + ledgerSeq;
335 for (; ledgerSeq < firstBatch; ++ledgerSeq)
336 {
337 env.close();
338
339 auto ledger = env.rpc("ledger", "validated");
340 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
341 }
342
343 store.rendezvous();
344
345 ledgerCheck(env, ledgerSeq - 2, 2);
346 BEAST_EXPECT(lastRotated == store.getLastRotated());
347
348 // This does not kick off a cleanup
349 canDelete = env.rpc("can_delete", std::to_string(ledgerSeq + (deleteInterval / 2)));
350 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
351 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq + (deleteInterval / 2));
352
353 store.rendezvous();
354
355 ledgerCheck(env, ledgerSeq - 2, 2);
356 BEAST_EXPECT(store.getLastRotated() == lastRotated);
357
358 {
359 // This kicks off a cleanup, but it stays small.
360 env.close();
361
362 auto ledger = env.rpc("ledger", "validated");
363 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
364 }
365
366 store.rendezvous();
367
368 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
369
370 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
371 lastRotated = ledgerSeq - 1;
372
373 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
374 {
375 // No cleanups in this loop.
376 env.close();
377
378 auto ledger = env.rpc("ledger", "validated");
379 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
380 }
381
382 store.rendezvous();
383
384 BEAST_EXPECT(store.getLastRotated() == lastRotated);
385
386 {
387 // This kicks off another cleanup.
388 env.close();
389
390 auto ledger = env.rpc("ledger", "validated");
391 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
392 }
393
394 store.rendezvous();
395
396 ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
397
398 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
399 lastRotated = ledgerSeq - 1;
400
401 // This does not kick off a cleanup
402 canDelete = env.rpc("can_delete", "always");
403 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
404 BEAST_EXPECT(
405 canDelete[jss::result][jss::can_delete] == std::numeric_limits<unsigned int>::max());
406
407 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
408 {
409 // No cleanups in this loop.
410 env.close();
411
412 auto ledger = env.rpc("ledger", "validated");
413 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
414 }
415
416 store.rendezvous();
417
418 BEAST_EXPECT(store.getLastRotated() == lastRotated);
419
420 {
421 // This kicks off another cleanup.
422 env.close();
423
424 auto ledger = env.rpc("ledger", "validated");
425 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
426 }
427
428 store.rendezvous();
429
430 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
431
432 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
433 lastRotated = ledgerSeq - 1;
434
435 // This does not kick off a cleanup
436 canDelete = env.rpc("can_delete", "now");
437 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
438 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
439
440 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
441 {
442 // No cleanups in this loop.
443 env.close();
444
445 auto ledger = env.rpc("ledger", "validated");
446 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
447 }
448
449 store.rendezvous();
450
451 BEAST_EXPECT(store.getLastRotated() == lastRotated);
452
453 {
454 // This kicks off another cleanup.
455 env.close();
456
457 auto ledger = env.rpc("ledger", "validated");
458 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
459 }
460
461 store.rendezvous();
462
463 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
464
465 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
466 lastRotated = ledgerSeq - 1;
467 }
468
471 {
473 boost::filesystem::path newPath;
474
475 if (!BEAST_EXPECT(path.size()))
476 return {};
477 newPath = path;
478 section.set("path", newPath.string());
479
481 section,
483 scheduler,
484 env.app().getJournal("NodeStoreTest"))};
485 backend->open();
486 return backend;
487 }
488
489 void
491 {
492 // The only purpose of this test is to ensure that if something that
493 // should never happen happens, we don't get a deadlock.
494 testcase("rotate with lock contention");
495
496 using namespace jtx;
497 Env env(*this, envconfig(onlineDelete));
498
500 // Create NodeStore with two backends to allow online deletion of data.
501 // Normally, SHAMapStoreImp handles all these details.
502 NodeStoreScheduler scheduler(env.app().getJobQueue());
503
504 std::string const writableDb = "write";
505 std::string const archiveDb = "archive";
506 auto writableBackend = makeBackendRotating(env, scheduler, writableDb);
507 auto archiveBackend = makeBackendRotating(env, scheduler, archiveDb);
508
509 constexpr int readThreads = 4;
510 auto nscfg = env.app().config().section(ConfigSection::nodeDatabase());
512 scheduler,
513 readThreads,
514 std::move(writableBackend),
515 std::move(archiveBackend),
516 nscfg,
517 env.app().getJournal("NodeStoreTest"));
518
520 // Check basic functionality
521 using namespace std::chrono_literals;
522 std::atomic<int> threadNum = 0;
523
524 {
525 auto newBackend = makeBackendRotating(env, scheduler, std::to_string(++threadNum));
526
527 auto const cb = [&](std::string const& writableName, std::string const& archiveName) {
528 BEAST_EXPECT(writableName == "1");
529 BEAST_EXPECT(archiveName == "write");
530 // Ensure that dbr functions can be called from within the
531 // callback
532 BEAST_EXPECT(dbr->getName() == "1");
533 };
534
535 dbr->rotate(std::move(newBackend), cb);
536 }
537 BEAST_EXPECT(threadNum == 1);
538 BEAST_EXPECT(dbr->getName() == "1");
539
541 // Do something stupid. Try to re-enter rotate from inside the callback.
542 {
543 auto const cb = [&](std::string const& writableName, std::string const& archiveName) {
544 BEAST_EXPECT(writableName == "3");
545 BEAST_EXPECT(archiveName == "2");
546 // Ensure that dbr functions can be called from within the
547 // callback
548 BEAST_EXPECT(dbr->getName() == "3");
549 };
550 auto const cbReentrant = [&](std::string const& writableName,
551 std::string const& archiveName) {
552 BEAST_EXPECT(writableName == "2");
553 BEAST_EXPECT(archiveName == "1");
554 auto newBackend = makeBackendRotating(env, scheduler, std::to_string(++threadNum));
555 // Reminder: doing this is stupid and should never happen
556 dbr->rotate(std::move(newBackend), cb);
557 };
558 auto newBackend = makeBackendRotating(env, scheduler, std::to_string(++threadNum));
559 dbr->rotate(std::move(newBackend), cbReentrant);
560 }
561
562 BEAST_EXPECT(threadNum == 3);
563 BEAST_EXPECT(dbr->getName() == "3");
564 }
565
566 void
567 run() override
568 {
569 testClear();
572 testRotate();
573 }
574};
575
576// VFALCO This test fails because of thread asynchronous issues
577BEAST_DEFINE_TESTSUITE(SHAMapStore, app, xrpl);
578
579} // namespace test
580} // 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:150
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:1150
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 std::size_t getTransactionCount()=0
getTransactionCount Returns the number of transactions.
virtual CountMinMax getLedgerCountMinMax()=0
getLedgerCountMinMax Returns the minimum ledger sequence, maximum ledger sequence and total number of...
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:17
virtual void rendezvous() const =0
Holds a collection of configuration values.
Definition BasicConfig.h:24
virtual JobQueue & getJobQueue()=0
virtual RelationalDatabase & getRelationalDatabase()=0
virtual beast::Journal getJournal(std::string const &name)=0
virtual SHAMapStore & getSHAMapStore()=0
static bool bad(Json::Value const &json, error_code_i error=rpcLGR_NOT_FOUND)
static auto onlineDelete(std::unique_ptr< Config > cfg)
static auto advisoryDelete(std::unique_ptr< Config > cfg)
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.
void transactionCheck(jtx::Env &env, int const rows)
static bool goodLedger(jtx::Env &env, Json::Value const &json, std::string ledgerID, bool checkDB=false)
A transaction testing environment.
Definition Env.h:122
Application & app()
Definition Env.h:259
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
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:847
Inject raw JSON.
Definition jtx_json.h:13
Add a path.
Definition paths.h:38
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:95
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:602
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)