rippled
Loading...
Searching...
No Matches
SHAMapStore_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2015 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/envconfig.h>
22#include <xrpld/app/main/Application.h>
23#include <xrpld/app/misc/SHAMapStore.h>
24#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
25#include <xrpld/core/ConfigSections.h>
26#include <xrpl/protocol/jss.h>
27
28namespace ripple {
29namespace test {
30
32{
33 static auto const deleteInterval = 8;
34
35 static auto
37 {
38 cfg->LEDGER_HISTORY = deleteInterval;
39 auto& section = cfg->section(ConfigSection::nodeDatabase());
40 section.set("online_delete", std::to_string(deleteInterval));
41 return cfg;
42 }
43
44 static auto
46 {
47 cfg = onlineDelete(std::move(cfg));
48 cfg->section(ConfigSection::nodeDatabase()).set("advisory_delete", "1");
49 return cfg;
50 }
51
52 bool
54 jtx::Env& env,
55 Json::Value const& json,
56 std::string ledgerID,
57 bool checkDB = false)
58 {
59 auto good = json.isMember(jss::result) &&
60 !RPC::contains_error(json[jss::result]) &&
61 json[jss::result][jss::ledger][jss::ledger_index] == ledgerID;
62 if (!good || !checkDB)
63 return good;
64
65 auto const seq = json[jss::result][jss::ledger_index].asUInt();
66
69 if (!oinfo)
70 return false;
71 const LedgerInfo& info = oinfo.value();
72
73 const std::string outHash = to_string(info.hash);
74 const LedgerIndex outSeq = info.seq;
75 const std::string outParentHash = to_string(info.parentHash);
76 const std::string outDrops = to_string(info.drops);
77 const std::uint64_t outCloseTime =
78 info.closeTime.time_since_epoch().count();
79 const std::uint64_t outParentCloseTime =
80 info.parentCloseTime.time_since_epoch().count();
81 const std::uint64_t outCloseTimeResolution =
83 const std::uint64_t outCloseFlags = info.closeFlags;
84 const std::string outAccountHash = to_string(info.accountHash);
85 const std::string outTxHash = to_string(info.txHash);
86
87 auto const& ledger = json[jss::result][jss::ledger];
88 return outHash == ledger[jss::ledger_hash].asString() &&
89 outSeq == seq &&
90 outParentHash == ledger[jss::parent_hash].asString() &&
91 outDrops == ledger[jss::total_coins].asString() &&
92 outCloseTime == ledger[jss::close_time].asUInt() &&
93 outParentCloseTime == ledger[jss::parent_close_time].asUInt() &&
94 outCloseTimeResolution ==
95 ledger[jss::close_time_resolution].asUInt() &&
96 outCloseFlags == ledger[jss::close_flags].asUInt() &&
97 outAccountHash == ledger[jss::account_hash].asString() &&
98 outTxHash == ledger[jss::transaction_hash].asString();
99 }
100
101 bool
103 {
104 return json.isMember(jss::result) &&
105 RPC::contains_error(json[jss::result]) &&
106 json[jss::result][jss::error_code] == error;
107 }
108
111 {
112 BEAST_EXPECT(
113 json.isMember(jss::result) &&
114 json[jss::result].isMember(jss::ledger) &&
115 json[jss::result][jss::ledger].isMember(jss::ledger_hash) &&
116 json[jss::result][jss::ledger][jss::ledger_hash].isString());
117 return json[jss::result][jss::ledger][jss::ledger_hash].asString();
118 }
119
120 void
121 ledgerCheck(jtx::Env& env, int const rows, int const first)
122 {
123 const auto [actualRows, actualFirst, actualLast] =
124 dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
125 ->getLedgerCountMinMax();
126
127 BEAST_EXPECT(actualRows == rows);
128 BEAST_EXPECT(actualFirst == first);
129 BEAST_EXPECT(actualLast == first + rows - 1);
130 }
131
132 void
133 transactionCheck(jtx::Env& env, int const rows)
134 {
135 BEAST_EXPECT(
136 dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
137 ->getTransactionCount() == rows);
138 }
139
140 void
141 accountTransactionCheck(jtx::Env& env, int const rows)
142 {
143 BEAST_EXPECT(
144 dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
145 ->getAccountTransactionCount() == rows);
146 }
147
148 int
150 {
151 using namespace std::chrono_literals;
152
153 auto& store = env.app().getSHAMapStore();
154
155 int ledgerSeq = 3;
156 store.rendezvous();
157 BEAST_EXPECT(!store.getLastRotated());
158
159 env.close();
160 store.rendezvous();
161
162 auto ledger = env.rpc("ledger", "validated");
163 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++)));
164
165 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
166 return ledgerSeq;
167 }
168
169public:
170 void
172 {
173 using namespace std::chrono_literals;
174
175 testcase("clearPrior");
176 using namespace jtx;
177
178 Env env(*this, envconfig(onlineDelete));
179
180 auto& store = env.app().getSHAMapStore();
181 env.fund(XRP(10000), noripple("alice"));
182
183 ledgerCheck(env, 1, 2);
184 transactionCheck(env, 0);
186
188
189 auto ledgerTmp = env.rpc("ledger", "0");
190 BEAST_EXPECT(bad(ledgerTmp));
191
192 ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1")));
193 BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
194
195 ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2")));
196 BEAST_EXPECT(goodLedger(env, ledgers[2], "2"));
197
198 ledgerTmp = env.rpc("ledger", "current");
199 BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
200
201 ledgerTmp = env.rpc("ledger", "4");
202 BEAST_EXPECT(bad(ledgerTmp));
203
204 ledgerTmp = env.rpc("ledger", "100");
205 BEAST_EXPECT(bad(ledgerTmp));
206
207 auto const firstSeq = waitForReady(env);
208 auto lastRotated = firstSeq - 1;
209
210 for (auto i = firstSeq + 1; i < deleteInterval + firstSeq; ++i)
211 {
212 env.fund(XRP(10000), noripple("test" + std::to_string(i)));
213 env.close();
214
215 ledgerTmp = env.rpc("ledger", "current");
216 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i)));
217 }
218 BEAST_EXPECT(store.getLastRotated() == lastRotated);
219
220 for (auto i = 3; i < deleteInterval + lastRotated; ++i)
221 {
222 ledgers.emplace(
223 std::make_pair(i, env.rpc("ledger", std::to_string(i))));
224 BEAST_EXPECT(
225 goodLedger(env, ledgers[i], std::to_string(i), true) &&
226 getHash(ledgers[i]).length());
227 }
228
229 ledgerCheck(env, deleteInterval + 1, 2);
232
233 {
234 // Closing one more ledger triggers a rotate
235 env.close();
236
237 auto ledger = env.rpc("ledger", "current");
238 BEAST_EXPECT(
239 goodLedger(env, ledger, std::to_string(deleteInterval + 4)));
240 }
241
242 store.rendezvous();
243
244 BEAST_EXPECT(store.getLastRotated() == deleteInterval + 3);
245 lastRotated = store.getLastRotated();
246 BEAST_EXPECT(lastRotated == 11);
247
248 // That took care of the fake hashes
249 ledgerCheck(env, deleteInterval + 1, 3);
252
253 // The last iteration of this loop should trigger a rotate
254 for (auto i = lastRotated - 1; i < lastRotated + deleteInterval - 1;
255 ++i)
256 {
257 env.close();
258
259 ledgerTmp = env.rpc("ledger", "current");
260 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i + 3)));
261
262 ledgers.emplace(
263 std::make_pair(i, env.rpc("ledger", std::to_string(i))));
264 BEAST_EXPECT(
265 store.getLastRotated() == lastRotated ||
266 i == lastRotated + deleteInterval - 2);
267 BEAST_EXPECT(
268 goodLedger(env, ledgers[i], std::to_string(i), true) &&
269 getHash(ledgers[i]).length());
270 }
271
272 store.rendezvous();
273
274 BEAST_EXPECT(store.getLastRotated() == deleteInterval + lastRotated);
275
276 ledgerCheck(env, deleteInterval + 1, lastRotated);
277 transactionCheck(env, 0);
279 }
280
281 void
283 {
284 testcase("automatic online_delete");
285 using namespace jtx;
286 using namespace std::chrono_literals;
287
288 Env env(*this, envconfig(onlineDelete));
289 auto& store = env.app().getSHAMapStore();
290
291 auto ledgerSeq = waitForReady(env);
292 auto lastRotated = ledgerSeq - 1;
293 BEAST_EXPECT(store.getLastRotated() == lastRotated);
294 BEAST_EXPECT(lastRotated != 2);
295
296 // Because advisory_delete is unset,
297 // "can_delete" is disabled.
298 auto const canDelete = env.rpc("can_delete");
299 BEAST_EXPECT(bad(canDelete, rpcNOT_ENABLED));
300
301 // Close ledgers without triggering a rotate
302 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
303 {
304 env.close();
305
306 auto ledger = env.rpc("ledger", "validated");
307 BEAST_EXPECT(
308 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
309 }
310
311 store.rendezvous();
312
313 // The database will always have back to ledger 2,
314 // regardless of lastRotated.
315 ledgerCheck(env, ledgerSeq - 2, 2);
316 BEAST_EXPECT(lastRotated == store.getLastRotated());
317
318 {
319 // Closing one more ledger triggers a rotate
320 env.close();
321
322 auto ledger = env.rpc("ledger", "validated");
323 BEAST_EXPECT(
324 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
325 }
326
327 store.rendezvous();
328
329 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
330 BEAST_EXPECT(lastRotated != store.getLastRotated());
331
332 lastRotated = store.getLastRotated();
333
334 // Close enough ledgers to trigger another rotate
335 for (; ledgerSeq < lastRotated + deleteInterval + 1; ++ledgerSeq)
336 {
337 env.close();
338
339 auto ledger = env.rpc("ledger", "validated");
340 BEAST_EXPECT(
341 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
342 }
343
344 store.rendezvous();
345
346 ledgerCheck(env, deleteInterval + 1, lastRotated);
347 BEAST_EXPECT(lastRotated != store.getLastRotated());
348 }
349
350 void
352 {
353 testcase("online_delete with advisory_delete");
354 using namespace jtx;
355 using namespace std::chrono_literals;
356
357 // Same config with advisory_delete enabled
358 Env env(*this, envconfig(advisoryDelete));
359 auto& store = env.app().getSHAMapStore();
360
361 auto ledgerSeq = waitForReady(env);
362 auto lastRotated = ledgerSeq - 1;
363 BEAST_EXPECT(store.getLastRotated() == lastRotated);
364 BEAST_EXPECT(lastRotated != 2);
365
366 auto canDelete = env.rpc("can_delete");
367 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
368 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
369
370 canDelete = env.rpc("can_delete", "never");
371 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
372 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
373
374 auto const firstBatch = deleteInterval + ledgerSeq;
375 for (; ledgerSeq < firstBatch; ++ledgerSeq)
376 {
377 env.close();
378
379 auto ledger = env.rpc("ledger", "validated");
380 BEAST_EXPECT(
381 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
382 }
383
384 store.rendezvous();
385
386 ledgerCheck(env, ledgerSeq - 2, 2);
387 BEAST_EXPECT(lastRotated == store.getLastRotated());
388
389 // This does not kick off a cleanup
390 canDelete = env.rpc(
391 "can_delete", std::to_string(ledgerSeq + deleteInterval / 2));
392 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
393 BEAST_EXPECT(
394 canDelete[jss::result][jss::can_delete] ==
395 ledgerSeq + deleteInterval / 2);
396
397 store.rendezvous();
398
399 ledgerCheck(env, ledgerSeq - 2, 2);
400 BEAST_EXPECT(store.getLastRotated() == lastRotated);
401
402 {
403 // This kicks off a cleanup, but it stays small.
404 env.close();
405
406 auto ledger = env.rpc("ledger", "validated");
407 BEAST_EXPECT(
408 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
409 }
410
411 store.rendezvous();
412
413 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
414
415 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
416 lastRotated = ledgerSeq - 1;
417
418 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
419 {
420 // No cleanups in this loop.
421 env.close();
422
423 auto ledger = env.rpc("ledger", "validated");
424 BEAST_EXPECT(
425 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
426 }
427
428 store.rendezvous();
429
430 BEAST_EXPECT(store.getLastRotated() == lastRotated);
431
432 {
433 // This kicks off another cleanup.
434 env.close();
435
436 auto ledger = env.rpc("ledger", "validated");
437 BEAST_EXPECT(
438 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
439 }
440
441 store.rendezvous();
442
443 ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
444
445 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
446 lastRotated = ledgerSeq - 1;
447
448 // This does not kick off a cleanup
449 canDelete = env.rpc("can_delete", "always");
450 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
451 BEAST_EXPECT(
452 canDelete[jss::result][jss::can_delete] ==
454
455 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
456 {
457 // No cleanups in this loop.
458 env.close();
459
460 auto ledger = env.rpc("ledger", "validated");
461 BEAST_EXPECT(
462 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
463 }
464
465 store.rendezvous();
466
467 BEAST_EXPECT(store.getLastRotated() == lastRotated);
468
469 {
470 // This kicks off another cleanup.
471 env.close();
472
473 auto ledger = env.rpc("ledger", "validated");
474 BEAST_EXPECT(
475 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
476 }
477
478 store.rendezvous();
479
480 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
481
482 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
483 lastRotated = ledgerSeq - 1;
484
485 // This does not kick off a cleanup
486 canDelete = env.rpc("can_delete", "now");
487 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
488 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
489
490 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
491 {
492 // No cleanups in this loop.
493 env.close();
494
495 auto ledger = env.rpc("ledger", "validated");
496 BEAST_EXPECT(
497 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
498 }
499
500 store.rendezvous();
501
502 BEAST_EXPECT(store.getLastRotated() == lastRotated);
503
504 {
505 // This kicks off another cleanup.
506 env.close();
507
508 auto ledger = env.rpc("ledger", "validated");
509 BEAST_EXPECT(
510 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
511 }
512
513 store.rendezvous();
514
515 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
516
517 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
518 lastRotated = ledgerSeq - 1;
519 }
520
521 void
522 run() override
523 {
524 testClear();
527 }
528};
529
530// VFALCO This test fails because of thread asynchronous issues
531BEAST_DEFINE_TESTSUITE(SHAMapStore, app, ripple);
532
533} // namespace test
534} // namespace ripple
Represents a JSON value.
Definition: json_value.h:147
A testsuite class.
Definition: suite.h:53
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:153
virtual SHAMapStore & getSHAMapStore()=0
virtual RelationalDatabase & getRelationalDatabase()=0
virtual std::optional< LedgerInfo > 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:37
virtual void rendezvous() const =0
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.
void ledgerCheck(jtx::Env &env, int const rows, int const first)
std::string getHash(Json::Value const &json)
static auto advisoryDelete(std::unique_ptr< Config > cfg)
bool bad(Json::Value const &json, error_code_i error=rpcLGR_NOT_FOUND)
void run() override
Runs the suite.
void accountTransactionCheck(jtx::Env &env, int const rows)
static auto onlineDelete(std::unique_ptr< Config > cfg)
void transactionCheck(jtx::Env &env, int const rows)
bool goodLedger(jtx::Env &env, Json::Value const &json, std::string ledgerID, bool checkDB=false)
A transaction testing environment.
Definition: Env.h:117
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
Application & app()
Definition: Env.h:255
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:764
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:237
Inject raw JSON.
Definition: jtx_json.h:32
T emplace(T... args)
T make_pair(T... args)
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:197
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
error_code_i
Definition: ErrorCodes.h:40
@ rpcLGR_NOT_FOUND
Definition: ErrorCodes.h:72
@ rpcNOT_ENABLED
Definition: ErrorCodes.h:59
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
static std::string nodeDatabase()
Information about the notional ledger backing the view.
Definition: LedgerHeader.h:34
NetClock::time_point closeTime
Definition: LedgerHeader.h:72
NetClock::duration closeTimeResolution
Definition: LedgerHeader.h:66
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
Set the sequence number on a JTx.
Definition: seq.h:34
T time_since_epoch(T... args)
T to_string(T... args)
T value(T... args)