rippled
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 <ripple/app/main/Application.h>
21 #include <ripple/app/misc/SHAMapStore.h>
22 #include <ripple/core/ConfigSections.h>
23 #include <ripple/core/DatabaseCon.h>
24 #include <ripple/core/SociDB.h>
25 #include <ripple/protocol/jss.h>
26 #include <test/jtx.h>
27 #include <test/jtx/envconfig.h>
28 
29 namespace ripple {
30 namespace test {
31 
32 class SHAMapStore_test : public beast::unit_test::suite
33 {
34  static auto const deleteInterval = 8;
35 
36  static
37  auto
39  {
40  cfg->LEDGER_HISTORY = deleteInterval;
41  auto& section = cfg->section(ConfigSection::nodeDatabase());
42  section.set("online_delete", std::to_string(deleteInterval));
43  return cfg;
44  }
45 
46  static
47  auto
49  {
50  cfg = onlineDelete(std::move(cfg));
51  cfg->section(ConfigSection::nodeDatabase())
52  .set("advisory_delete", "1");
53  return cfg;
54  }
55 
56  bool goodLedger(jtx::Env& env, Json::Value const& json,
57  std::string ledgerID, 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  std::string outHash;
67  LedgerIndex outSeq;
68  std::string outParentHash;
69  std::string outDrops;
70  std::uint64_t outCloseTime;
71  std::uint64_t outParentCloseTime;
72  std::uint64_t outCloseTimeResolution;
73  std::uint64_t outCloseFlags;
74  std::string outAccountHash;
75  std::string outTxHash;
76 
77  {
78  auto db = env.app().getLedgerDB().checkoutDb();
79 
80  *db << "SELECT LedgerHash,LedgerSeq,PrevHash,TotalCoins, "
81  "ClosingTime,PrevClosingTime,CloseTimeRes,CloseFlags, "
82  "AccountSetHash,TransSetHash "
83  "FROM Ledgers "
84  "WHERE LedgerSeq = :seq",
85  soci::use(seq),
86  soci::into(outHash),
87  soci::into(outSeq),
88  soci::into(outParentHash),
89  soci::into(outDrops),
90  soci::into(outCloseTime),
91  soci::into(outParentCloseTime),
92  soci::into(outCloseTimeResolution),
93  soci::into(outCloseFlags),
94  soci::into(outAccountHash),
95  soci::into(outTxHash);
96  }
97 
98  auto const& ledger = json[jss::result][jss::ledger];
99  return outHash == ledger[jss::hash].asString() &&
100  outSeq == seq &&
101  outParentHash == ledger[jss::parent_hash].asString() &&
102  outDrops == ledger[jss::total_coins].asString() &&
103  outCloseTime == ledger[jss::close_time].asUInt() &&
104  outParentCloseTime == ledger[jss::parent_close_time].asUInt() &&
105  outCloseTimeResolution == ledger[jss::close_time_resolution].asUInt() &&
106  outCloseFlags == ledger[jss::close_flags].asUInt() &&
107  outAccountHash == ledger[jss::account_hash].asString() &&
108  outTxHash == ledger[jss::transaction_hash].asString();
109  }
110 
112  {
113  return json.isMember(jss::result)
114  && RPC::contains_error(json[jss::result])
115  && json[jss::result][jss::error_code] == error;
116  }
117 
119  {
120  BEAST_EXPECT(json.isMember(jss::result) &&
121  json[jss::result].isMember(jss::ledger) &&
122  json[jss::result][jss::ledger].isMember(jss::hash) &&
123  json[jss::result][jss::ledger][jss::hash].isString());
124  return json[jss::result][jss::ledger][jss::hash].asString();
125  }
126 
127  void ledgerCheck(jtx::Env& env, int const rows,
128  int const first)
129  {
130  auto db = env.app().getLedgerDB().checkoutDb();
131 
132  int actualRows, actualFirst, actualLast;
133  *db << "SELECT count(*) AS rows, "
134  "min(LedgerSeq) as first, "
135  "max(LedgerSeq) as last "
136  "FROM Ledgers;",
137  soci::into(actualRows),
138  soci::into(actualFirst),
139  soci::into(actualLast);
140 
141  BEAST_EXPECT(actualRows == rows);
142  BEAST_EXPECT(actualFirst == first);
143  BEAST_EXPECT(actualLast == first + rows - 1);
144 
145  }
146 
147  void transactionCheck(jtx::Env& env, int const rows)
148  {
149  auto db = env.app().getTxnDB().checkoutDb();
150 
151  int actualRows;
152  *db << "SELECT count(*) AS rows "
153  "FROM Transactions;",
154  soci::into(actualRows);
155 
156  BEAST_EXPECT(actualRows == rows);
157  }
158 
159  void accountTransactionCheck(jtx::Env& env, int const rows)
160  {
161  auto db = env.app().getTxnDB().checkoutDb();
162 
163  int actualRows;
164  *db << "SELECT count(*) AS rows "
165  "FROM AccountTransactions;",
166  soci::into(actualRows);
167 
168  BEAST_EXPECT(actualRows == rows);
169  }
170 
172  {
173  using namespace std::chrono_literals;
174 
175  auto& store = env.app().getSHAMapStore();
176 
177  int ledgerSeq = 3;
178  store.rendezvous();
179  BEAST_EXPECT(!store.getLastRotated());
180 
181  env.close();
182  store.rendezvous();
183 
184  auto ledger = env.rpc("ledger", "validated");
185  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++)));
186 
187  BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
188  return ledgerSeq;
189  }
190 
191 public:
192  void testClear()
193  {
194  using namespace std::chrono_literals;
195 
196  testcase("clearPrior");
197  using namespace jtx;
198 
199  Env env(*this, envconfig(onlineDelete));
200 
201  auto& store = env.app().getSHAMapStore();
202  env.fund(XRP(10000), noripple("alice"));
203 
204  ledgerCheck(env, 1, 2);
205  transactionCheck(env, 0);
206  accountTransactionCheck(env, 0);
207 
209 
210  auto ledgerTmp = env.rpc("ledger", "0");
211  BEAST_EXPECT(bad(ledgerTmp));
212 
213  ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1")));
214  BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
215 
216  ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2")));
217  BEAST_EXPECT(goodLedger(env, ledgers[2], "2"));
218 
219  ledgerTmp = env.rpc("ledger", "current");
220  BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
221 
222  ledgerTmp = env.rpc("ledger", "4");
223  BEAST_EXPECT(bad(ledgerTmp));
224 
225  ledgerTmp = env.rpc("ledger", "100");
226  BEAST_EXPECT(bad(ledgerTmp));
227 
228  auto const firstSeq = waitForReady(env);
229  auto lastRotated = firstSeq - 1;
230 
231  for (auto i = firstSeq + 1; i < deleteInterval + firstSeq; ++i)
232  {
233  env.fund(XRP(10000), noripple("test" + std::to_string(i)));
234  env.close();
235 
236  ledgerTmp = env.rpc("ledger", "current");
237  BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i)));
238  }
239  BEAST_EXPECT(store.getLastRotated() == lastRotated);
240 
241  for (auto i = 3; i < deleteInterval + lastRotated; ++i)
242  {
243  ledgers.emplace(
244  std::make_pair(i, env.rpc("ledger", std::to_string(i))));
245  BEAST_EXPECT(goodLedger(env, ledgers[i], std::to_string(i), true) &&
246  getHash(ledgers[i]).length());
247  }
248 
249  ledgerCheck(env, deleteInterval + 1, 2);
252 
253  {
254  // Closing one more ledger triggers a rotate
255  env.close();
256 
257  auto ledger = env.rpc("ledger", "current");
258  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(deleteInterval + 4)));
259  }
260 
261  store.rendezvous();
262 
263  BEAST_EXPECT(store.getLastRotated() == deleteInterval + 3);
264  lastRotated = store.getLastRotated();
265  BEAST_EXPECT(lastRotated == 11);
266 
267  // That took care of the fake hashes
268  ledgerCheck(env, deleteInterval + 1, 3);
271 
272  // The last iteration of this loop should trigger a rotate
273  for (auto i = lastRotated - 1; i < lastRotated + deleteInterval - 1; ++i)
274  {
275  env.close();
276 
277  ledgerTmp = env.rpc("ledger", "current");
278  BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i + 3)));
279 
280  ledgers.emplace(
281  std::make_pair(i, env.rpc("ledger", std::to_string(i))));
282  BEAST_EXPECT(store.getLastRotated() == lastRotated ||
283  i == lastRotated + deleteInterval - 2);
284  BEAST_EXPECT(goodLedger(env, ledgers[i], std::to_string(i), true) &&
285  getHash(ledgers[i]).length());
286  }
287 
288  store.rendezvous();
289 
290  BEAST_EXPECT(store.getLastRotated() == deleteInterval + lastRotated);
291 
292  ledgerCheck(env, deleteInterval + 1, lastRotated);
293  transactionCheck(env, 0);
294  accountTransactionCheck(env, 0);
295 
296  }
297 
299  {
300  testcase("automatic online_delete");
301  using namespace jtx;
302  using namespace std::chrono_literals;
303 
304  Env env(*this, envconfig(onlineDelete));
305  auto& store = env.app().getSHAMapStore();
306 
307  auto ledgerSeq = waitForReady(env);
308  auto lastRotated = ledgerSeq - 1;
309  BEAST_EXPECT(store.getLastRotated() == lastRotated);
310  BEAST_EXPECT(lastRotated != 2);
311 
312  // Because advisory_delete is unset,
313  // "can_delete" is disabled.
314  auto const canDelete = env.rpc("can_delete");
315  BEAST_EXPECT(bad(canDelete, rpcNOT_ENABLED));
316 
317  // Close ledgers without triggering a rotate
318  for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
319  {
320  env.close();
321 
322  auto ledger = env.rpc("ledger", "validated");
323  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
324  }
325 
326  store.rendezvous();
327 
328  // The database will always have back to ledger 2,
329  // regardless of lastRotated.
330  ledgerCheck(env, ledgerSeq - 2, 2);
331  BEAST_EXPECT(lastRotated == store.getLastRotated());
332 
333  {
334  // Closing one more ledger triggers a rotate
335  env.close();
336 
337  auto ledger = env.rpc("ledger", "validated");
338  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
339  }
340 
341  store.rendezvous();
342 
343  ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
344  BEAST_EXPECT(lastRotated != store.getLastRotated());
345 
346  lastRotated = store.getLastRotated();
347 
348  // Close enough ledgers to trigger another rotate
349  for (; ledgerSeq < lastRotated + deleteInterval + 1; ++ledgerSeq)
350  {
351  env.close();
352 
353  auto ledger = env.rpc("ledger", "validated");
354  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
355  }
356 
357  store.rendezvous();
358 
359  ledgerCheck(env, deleteInterval + 1, lastRotated);
360  BEAST_EXPECT(lastRotated != store.getLastRotated());
361  }
362 
364  {
365  testcase("online_delete with advisory_delete");
366  using namespace jtx;
367  using namespace std::chrono_literals;
368 
369  // Same config with advisory_delete enabled
370  Env env(*this, envconfig(advisoryDelete));
371  auto& store = env.app().getSHAMapStore();
372 
373  auto ledgerSeq = waitForReady(env);
374  auto lastRotated = ledgerSeq - 1;
375  BEAST_EXPECT(store.getLastRotated() == lastRotated);
376  BEAST_EXPECT(lastRotated != 2);
377 
378  auto canDelete = env.rpc("can_delete");
379  BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
380  BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
381 
382  canDelete = env.rpc("can_delete", "never");
383  BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
384  BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
385 
386  auto const firstBatch = deleteInterval + ledgerSeq;
387  for (; ledgerSeq < firstBatch; ++ledgerSeq)
388  {
389  env.close();
390 
391  auto ledger = env.rpc("ledger", "validated");
392  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
393  }
394 
395  store.rendezvous();
396 
397  ledgerCheck(env, ledgerSeq - 2, 2);
398  BEAST_EXPECT(lastRotated == store.getLastRotated());
399 
400  // This does not kick off a cleanup
401  canDelete = env.rpc("can_delete", std::to_string(
402  ledgerSeq + deleteInterval / 2));
403  BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
404  BEAST_EXPECT(canDelete[jss::result][jss::can_delete] ==
405  ledgerSeq + deleteInterval / 2);
406 
407  store.rendezvous();
408 
409  ledgerCheck(env, ledgerSeq - 2, 2);
410  BEAST_EXPECT(store.getLastRotated() == lastRotated);
411 
412  {
413  // This kicks off a cleanup, but it stays small.
414  env.close();
415 
416  auto ledger = env.rpc("ledger", "validated");
417  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
418  }
419 
420  store.rendezvous();
421 
422  ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
423 
424  BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
425  lastRotated = ledgerSeq - 1;
426 
427  for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
428  {
429  // No cleanups in this loop.
430  env.close();
431 
432  auto ledger = env.rpc("ledger", "validated");
433  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
434  }
435 
436  store.rendezvous();
437 
438  BEAST_EXPECT(store.getLastRotated() == lastRotated);
439 
440  {
441  // This kicks off another cleanup.
442  env.close();
443 
444  auto ledger = env.rpc("ledger", "validated");
445  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
446  }
447 
448  store.rendezvous();
449 
450  ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
451 
452  BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
453  lastRotated = ledgerSeq - 1;
454 
455  // This does not kick off a cleanup
456  canDelete = env.rpc("can_delete", "always");
457  BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
458  BEAST_EXPECT(canDelete[jss::result][jss::can_delete] ==
460 
461  for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
462  {
463  // No cleanups in this loop.
464  env.close();
465 
466  auto ledger = env.rpc("ledger", "validated");
467  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
468  }
469 
470  store.rendezvous();
471 
472  BEAST_EXPECT(store.getLastRotated() == lastRotated);
473 
474  {
475  // This kicks off another cleanup.
476  env.close();
477 
478  auto ledger = env.rpc("ledger", "validated");
479  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
480  }
481 
482  store.rendezvous();
483 
484  ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
485 
486  BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
487  lastRotated = ledgerSeq - 1;
488 
489  // This does not kick off a cleanup
490  canDelete = env.rpc("can_delete", "now");
491  BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
492  BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
493 
494  for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
495  {
496  // No cleanups in this loop.
497  env.close();
498 
499  auto ledger = env.rpc("ledger", "validated");
500  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
501  }
502 
503  store.rendezvous();
504 
505  BEAST_EXPECT(store.getLastRotated() == lastRotated);
506 
507  {
508  // This kicks off another cleanup.
509  env.close();
510 
511  auto ledger = env.rpc("ledger", "validated");
512  BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
513  }
514 
515  store.rendezvous();
516 
517  ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
518 
519  BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
520  lastRotated = ledgerSeq - 1;
521  }
522 
523  void run() override
524  {
525  testClear();
526  testAutomatic();
527  testCanDelete();
528  }
529 };
530 
531 // VFALCO This test fails because of thread asynchronous issues
533 
534 }
535 }
ripple::test::jtx::json
Inject raw JSON.
Definition: jtx_json.h:31
ripple::test::SHAMapStore_test::run
void run() override
Definition: SHAMapStore_test.cpp:523
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:109
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
std::string
STL class.
ripple::test::SHAMapStore_test::testAutomatic
void testAutomatic()
Definition: SHAMapStore_test.cpp:298
ripple::SHAMapStore
class to create database, launch online delete thread, and related SQLite database
Definition: SHAMapStore.h:36
ripple::test::SHAMapStore_test::deleteInterval
static const auto deleteInterval
Definition: SHAMapStore_test.cpp:34
std::map::emplace
T emplace(T... args)
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:237
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:52
ripple::rpcLGR_NOT_FOUND
@ rpcLGR_NOT_FOUND
Definition: ErrorCodes.h:73
ripple::SHAMapStore::rendezvous
virtual void rendezvous() const =0
ripple::error_code_i
error_code_i
Definition: ErrorCodes.h:40
ripple::test::SHAMapStore_test::advisoryDelete
static auto advisoryDelete(std::unique_ptr< Config > cfg)
Definition: SHAMapStore_test.cpp:48
ripple::DatabaseCon::checkoutDb
LockedSociSession checkoutDb()
Definition: DatabaseCon.h:123
ripple::RPC::contains_error
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:196
std::to_string
T to_string(T... args)
ripple::rpcNOT_ENABLED
@ rpcNOT_ENABLED
Definition: ErrorCodes.h:60
ripple::Application::getLedgerDB
virtual DatabaseCon & getLedgerDB()=0
std::uint32_t
std::map
STL class.
ripple::test::SHAMapStore_test::transactionCheck
void transactionCheck(jtx::Env &env, int const rows)
Definition: SHAMapStore_test.cpp:147
ripple::test::jtx::seq
Set the sequence number on a JTx.
Definition: seq.h:32
ripple::test::SHAMapStore_test::testCanDelete
void testCanDelete()
Definition: SHAMapStore_test.cpp:363
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::test::jtx::noripple
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
Definition: Env.h:64
ripple::test::jtx::Env::close
void close(NetClock::time_point closeTime, boost::optional< std::chrono::milliseconds > consensusDelay=boost::none)
Close and advance the ledger.
Definition: Env.cpp:114
ripple::test::SHAMapStore_test::testClear
void testClear()
Definition: SHAMapStore_test.cpp:192
ripple::test::SHAMapStore_test::ledgerCheck
void ledgerCheck(jtx::Env &env, int const rows, int const first)
Definition: SHAMapStore_test.cpp:127
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:214
ripple::test::SHAMapStore_test::accountTransactionCheck
void accountTransactionCheck(jtx::Env &env, int const rows)
Definition: SHAMapStore_test.cpp:159
ripple::test::SHAMapStore_test::waitForReady
int waitForReady(jtx::Env &env)
Definition: SHAMapStore_test.cpp:171
ripple::test::SHAMapStore_test::goodLedger
bool goodLedger(jtx::Env &env, Json::Value const &json, std::string ledgerID, bool checkDB=false)
Definition: SHAMapStore_test.cpp:56
std::make_pair
T make_pair(T... args)
ripple::Application::getSHAMapStore
virtual SHAMapStore & getSHAMapStore()=0
ripple::test::SHAMapStore_test
Definition: SHAMapStore_test.cpp:32
ripple::test::SHAMapStore_test::onlineDelete
static auto onlineDelete(std::unique_ptr< Config > cfg)
Definition: SHAMapStore_test.cpp:38
std::unique_ptr
STL class.
std::numeric_limits
ripple::test::SHAMapStore_test::bad
bool bad(Json::Value const &json, error_code_i error=rpcLGR_NOT_FOUND)
Definition: SHAMapStore_test.cpp:111
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117
ripple::ConfigSection::nodeDatabase
static std::string nodeDatabase()
Definition: ConfigSections.h:32
ripple::test::jtx::Env::rpc
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:688
Json::Value
Represents a JSON value.
Definition: json_value.h:141
ripple::Application::getTxnDB
virtual DatabaseCon & getTxnDB()=0
ripple::test::SHAMapStore_test::getHash
std::string getHash(Json::Value const &json)
Definition: SHAMapStore_test.cpp:118