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 ripple {
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
39 jtx::Env& env,
40 Json::Value const& json,
41 std::string ledgerID,
42 bool checkDB = false)
43 {
44 auto good = json.isMember(jss::result) &&
45 !RPC::contains_error(json[jss::result]) &&
46 json[jss::result][jss::ledger][jss::ledger_index] == ledgerID;
47 if (!good || !checkDB)
48 return good;
49
50 auto const seq = json[jss::result][jss::ledger_index].asUInt();
51
54 if (!oinfo)
55 return false;
56 LedgerInfo const& info = oinfo.value();
57
58 std::string const outHash = to_string(info.hash);
59 LedgerIndex const outSeq = info.seq;
60 std::string const outParentHash = to_string(info.parentHash);
61 std::string const outDrops = to_string(info.drops);
62 std::uint64_t const outCloseTime =
63 info.closeTime.time_since_epoch().count();
64 std::uint64_t const outParentCloseTime =
65 info.parentCloseTime.time_since_epoch().count();
66 std::uint64_t const outCloseTimeResolution =
68 std::uint64_t const outCloseFlags = info.closeFlags;
69 std::string const outAccountHash = to_string(info.accountHash);
70 std::string const outTxHash = to_string(info.txHash);
71
72 auto const& ledger = json[jss::result][jss::ledger];
73 return outHash == ledger[jss::ledger_hash].asString() &&
74 outSeq == seq &&
75 outParentHash == ledger[jss::parent_hash].asString() &&
76 outDrops == ledger[jss::total_coins].asString() &&
77 outCloseTime == ledger[jss::close_time].asUInt() &&
78 outParentCloseTime == ledger[jss::parent_close_time].asUInt() &&
79 outCloseTimeResolution ==
80 ledger[jss::close_time_resolution].asUInt() &&
81 outCloseFlags == ledger[jss::close_flags].asUInt() &&
82 outAccountHash == ledger[jss::account_hash].asString() &&
83 outTxHash == ledger[jss::transaction_hash].asString();
84 }
85
86 bool
88 {
89 return json.isMember(jss::result) &&
90 RPC::contains_error(json[jss::result]) &&
91 json[jss::result][jss::error_code] == error;
92 }
93
96 {
97 BEAST_EXPECT(
98 json.isMember(jss::result) &&
99 json[jss::result].isMember(jss::ledger) &&
100 json[jss::result][jss::ledger].isMember(jss::ledger_hash) &&
101 json[jss::result][jss::ledger][jss::ledger_hash].isString());
102 return json[jss::result][jss::ledger][jss::ledger_hash].asString();
103 }
104
105 void
106 ledgerCheck(jtx::Env& env, int const rows, int const first)
107 {
108 auto const [actualRows, actualFirst, actualLast] =
109 dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
110 ->getLedgerCountMinMax();
111
112 BEAST_EXPECT(actualRows == rows);
113 BEAST_EXPECT(actualFirst == first);
114 BEAST_EXPECT(actualLast == first + rows - 1);
115 }
116
117 void
118 transactionCheck(jtx::Env& env, int const rows)
119 {
120 BEAST_EXPECT(
121 dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
122 ->getTransactionCount() == rows);
123 }
124
125 void
126 accountTransactionCheck(jtx::Env& env, int const rows)
127 {
128 BEAST_EXPECT(
129 dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
130 ->getAccountTransactionCount() == rows);
131 }
132
133 int
135 {
136 using namespace std::chrono_literals;
137
138 auto& store = env.app().getSHAMapStore();
139
140 int ledgerSeq = 3;
141 store.rendezvous();
142 BEAST_EXPECT(!store.getLastRotated());
143
144 env.close();
145 store.rendezvous();
146
147 auto ledger = env.rpc("ledger", "validated");
148 BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++)));
149
150 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
151 return ledgerSeq;
152 }
153
154public:
155 void
157 {
158 using namespace std::chrono_literals;
159
160 testcase("clearPrior");
161 using namespace jtx;
162
163 Env env(*this, envconfig(onlineDelete));
164
165 auto& store = env.app().getSHAMapStore();
166 env.fund(XRP(10000), noripple("alice"));
167
168 ledgerCheck(env, 1, 2);
169 transactionCheck(env, 0);
171
173
174 auto ledgerTmp = env.rpc("ledger", "0");
175 BEAST_EXPECT(bad(ledgerTmp));
176
177 ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1")));
178 BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
179
180 ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2")));
181 BEAST_EXPECT(goodLedger(env, ledgers[2], "2"));
182
183 ledgerTmp = env.rpc("ledger", "current");
184 BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
185
186 ledgerTmp = env.rpc("ledger", "4");
187 BEAST_EXPECT(bad(ledgerTmp));
188
189 ledgerTmp = env.rpc("ledger", "100");
190 BEAST_EXPECT(bad(ledgerTmp));
191
192 auto const firstSeq = waitForReady(env);
193 auto lastRotated = firstSeq - 1;
194
195 for (auto i = firstSeq + 1; i < deleteInterval + firstSeq; ++i)
196 {
197 env.fund(XRP(10000), noripple("test" + std::to_string(i)));
198 env.close();
199
200 ledgerTmp = env.rpc("ledger", "current");
201 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i)));
202 }
203 BEAST_EXPECT(store.getLastRotated() == lastRotated);
204
205 for (auto i = 3; i < deleteInterval + lastRotated; ++i)
206 {
207 ledgers.emplace(
208 std::make_pair(i, env.rpc("ledger", std::to_string(i))));
209 BEAST_EXPECT(
210 goodLedger(env, ledgers[i], std::to_string(i), true) &&
211 getHash(ledgers[i]).length());
212 }
213
214 ledgerCheck(env, deleteInterval + 1, 2);
217
218 {
219 // Closing one more ledger triggers a rotate
220 env.close();
221
222 auto ledger = env.rpc("ledger", "current");
223 BEAST_EXPECT(
224 goodLedger(env, ledger, std::to_string(deleteInterval + 4)));
225 }
226
227 store.rendezvous();
228
229 BEAST_EXPECT(store.getLastRotated() == deleteInterval + 3);
230 lastRotated = store.getLastRotated();
231 BEAST_EXPECT(lastRotated == 11);
232
233 // That took care of the fake hashes
234 ledgerCheck(env, deleteInterval + 1, 3);
237
238 // The last iteration of this loop should trigger a rotate
239 for (auto i = lastRotated - 1; i < lastRotated + deleteInterval - 1;
240 ++i)
241 {
242 env.close();
243
244 ledgerTmp = env.rpc("ledger", "current");
245 BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i + 3)));
246
247 ledgers.emplace(
248 std::make_pair(i, env.rpc("ledger", std::to_string(i))));
249 BEAST_EXPECT(
250 store.getLastRotated() == lastRotated ||
251 i == lastRotated + deleteInterval - 2);
252 BEAST_EXPECT(
253 goodLedger(env, ledgers[i], std::to_string(i), true) &&
254 getHash(ledgers[i]).length());
255 }
256
257 store.rendezvous();
258
259 BEAST_EXPECT(store.getLastRotated() == deleteInterval + lastRotated);
260
261 ledgerCheck(env, deleteInterval + 1, lastRotated);
262 transactionCheck(env, 0);
264 }
265
266 void
268 {
269 testcase("automatic online_delete");
270 using namespace jtx;
271 using namespace std::chrono_literals;
272
273 Env env(*this, envconfig(onlineDelete));
274 auto& store = env.app().getSHAMapStore();
275
276 auto ledgerSeq = waitForReady(env);
277 auto lastRotated = ledgerSeq - 1;
278 BEAST_EXPECT(store.getLastRotated() == lastRotated);
279 BEAST_EXPECT(lastRotated != 2);
280
281 // Because advisory_delete is unset,
282 // "can_delete" is disabled.
283 auto const canDelete = env.rpc("can_delete");
284 BEAST_EXPECT(bad(canDelete, rpcNOT_ENABLED));
285
286 // Close ledgers without triggering a rotate
287 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
288 {
289 env.close();
290
291 auto ledger = env.rpc("ledger", "validated");
292 BEAST_EXPECT(
293 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
294 }
295
296 store.rendezvous();
297
298 // The database will always have back to ledger 2,
299 // regardless of lastRotated.
300 ledgerCheck(env, ledgerSeq - 2, 2);
301 BEAST_EXPECT(lastRotated == store.getLastRotated());
302
303 {
304 // Closing one more ledger triggers a rotate
305 env.close();
306
307 auto ledger = env.rpc("ledger", "validated");
308 BEAST_EXPECT(
309 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
310 }
311
312 store.rendezvous();
313
314 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
315 BEAST_EXPECT(lastRotated != store.getLastRotated());
316
317 lastRotated = store.getLastRotated();
318
319 // Close enough ledgers to trigger another rotate
320 for (; ledgerSeq < lastRotated + deleteInterval + 1; ++ledgerSeq)
321 {
322 env.close();
323
324 auto ledger = env.rpc("ledger", "validated");
325 BEAST_EXPECT(
326 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
327 }
328
329 store.rendezvous();
330
331 ledgerCheck(env, deleteInterval + 1, lastRotated);
332 BEAST_EXPECT(lastRotated != store.getLastRotated());
333 }
334
335 void
337 {
338 testcase("online_delete with advisory_delete");
339 using namespace jtx;
340 using namespace std::chrono_literals;
341
342 // Same config with advisory_delete enabled
343 Env env(*this, envconfig(advisoryDelete));
344 auto& store = env.app().getSHAMapStore();
345
346 auto ledgerSeq = waitForReady(env);
347 auto lastRotated = ledgerSeq - 1;
348 BEAST_EXPECT(store.getLastRotated() == lastRotated);
349 BEAST_EXPECT(lastRotated != 2);
350
351 auto canDelete = env.rpc("can_delete");
352 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
353 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
354
355 canDelete = env.rpc("can_delete", "never");
356 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
357 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0);
358
359 auto const firstBatch = deleteInterval + ledgerSeq;
360 for (; ledgerSeq < firstBatch; ++ledgerSeq)
361 {
362 env.close();
363
364 auto ledger = env.rpc("ledger", "validated");
365 BEAST_EXPECT(
366 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
367 }
368
369 store.rendezvous();
370
371 ledgerCheck(env, ledgerSeq - 2, 2);
372 BEAST_EXPECT(lastRotated == store.getLastRotated());
373
374 // This does not kick off a cleanup
375 canDelete = env.rpc(
376 "can_delete", std::to_string(ledgerSeq + deleteInterval / 2));
377 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
378 BEAST_EXPECT(
379 canDelete[jss::result][jss::can_delete] ==
380 ledgerSeq + deleteInterval / 2);
381
382 store.rendezvous();
383
384 ledgerCheck(env, ledgerSeq - 2, 2);
385 BEAST_EXPECT(store.getLastRotated() == lastRotated);
386
387 {
388 // This kicks off a cleanup, but it stays small.
389 env.close();
390
391 auto ledger = env.rpc("ledger", "validated");
392 BEAST_EXPECT(
393 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
394 }
395
396 store.rendezvous();
397
398 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
399
400 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
401 lastRotated = ledgerSeq - 1;
402
403 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
404 {
405 // No cleanups in this loop.
406 env.close();
407
408 auto ledger = env.rpc("ledger", "validated");
409 BEAST_EXPECT(
410 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
411 }
412
413 store.rendezvous();
414
415 BEAST_EXPECT(store.getLastRotated() == lastRotated);
416
417 {
418 // This kicks off another cleanup.
419 env.close();
420
421 auto ledger = env.rpc("ledger", "validated");
422 BEAST_EXPECT(
423 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
424 }
425
426 store.rendezvous();
427
428 ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
429
430 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
431 lastRotated = ledgerSeq - 1;
432
433 // This does not kick off a cleanup
434 canDelete = env.rpc("can_delete", "always");
435 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
436 BEAST_EXPECT(
437 canDelete[jss::result][jss::can_delete] ==
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(
447 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
448 }
449
450 store.rendezvous();
451
452 BEAST_EXPECT(store.getLastRotated() == lastRotated);
453
454 {
455 // This kicks off another cleanup.
456 env.close();
457
458 auto ledger = env.rpc("ledger", "validated");
459 BEAST_EXPECT(
460 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
461 }
462
463 store.rendezvous();
464
465 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
466
467 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
468 lastRotated = ledgerSeq - 1;
469
470 // This does not kick off a cleanup
471 canDelete = env.rpc("can_delete", "now");
472 BEAST_EXPECT(!RPC::contains_error(canDelete[jss::result]));
473 BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1);
474
475 for (; ledgerSeq < lastRotated + deleteInterval; ++ledgerSeq)
476 {
477 // No cleanups in this loop.
478 env.close();
479
480 auto ledger = env.rpc("ledger", "validated");
481 BEAST_EXPECT(
482 goodLedger(env, ledger, std::to_string(ledgerSeq), true));
483 }
484
485 store.rendezvous();
486
487 BEAST_EXPECT(store.getLastRotated() == lastRotated);
488
489 {
490 // This kicks off another cleanup.
491 env.close();
492
493 auto ledger = env.rpc("ledger", "validated");
494 BEAST_EXPECT(
495 goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
496 }
497
498 store.rendezvous();
499
500 ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
501
502 BEAST_EXPECT(store.getLastRotated() == ledgerSeq - 1);
503 lastRotated = ledgerSeq - 1;
504 }
505
508 jtx::Env& env,
509 NodeStoreScheduler& scheduler,
511 {
512 Section section{
514 boost::filesystem::path newPath;
515
516 if (!BEAST_EXPECT(path.size()))
517 return {};
518 newPath = path;
519 section.set("path", newPath.string());
520
522 section,
525 scheduler,
526 env.app().logs().journal("NodeStoreTest"))};
527 backend->open();
528 return backend;
529 }
530
531 void
533 {
534 // The only purpose of this test is to ensure that if something that
535 // should never happen happens, we don't get a deadlock.
536 testcase("rotate with lock contention");
537
538 using namespace jtx;
539 Env env(*this, envconfig(onlineDelete));
540
542 // Create the backend. Normally, SHAMapStoreImp handles all these
543 // details
544 auto nscfg = env.app().config().section(ConfigSection::nodeDatabase());
545
546 // Provide default values:
547 if (!nscfg.exists("cache_size"))
548 nscfg.set(
549 "cache_size",
552
553 if (!nscfg.exists("cache_age"))
554 nscfg.set(
555 "cache_age",
558
559 NodeStoreScheduler scheduler(env.app().getJobQueue());
560
561 std::string const writableDb = "write";
562 std::string const archiveDb = "archive";
563 auto writableBackend = makeBackendRotating(env, scheduler, writableDb);
564 auto archiveBackend = makeBackendRotating(env, scheduler, archiveDb);
565
566 // Create NodeStore with two backends to allow online deletion of
567 // data
568 constexpr int readThreads = 4;
570 scheduler,
571 readThreads,
572 std::move(writableBackend),
573 std::move(archiveBackend),
574 nscfg,
575 env.app().logs().journal("NodeStoreTest"));
576
578 // Check basic functionality
579 using namespace std::chrono_literals;
580 std::atomic<int> threadNum = 0;
581
582 {
583 auto newBackend = makeBackendRotating(
584 env, scheduler, std::to_string(++threadNum));
585
586 auto const cb = [&](std::string const& writableName,
587 std::string const& archiveName) {
588 BEAST_EXPECT(writableName == "1");
589 BEAST_EXPECT(archiveName == "write");
590 // Ensure that dbr functions can be called from within the
591 // callback
592 BEAST_EXPECT(dbr->getName() == "1");
593 };
594
595 dbr->rotate(std::move(newBackend), cb);
596 }
597 BEAST_EXPECT(threadNum == 1);
598 BEAST_EXPECT(dbr->getName() == "1");
599
601 // Do something stupid. Try to re-enter rotate from inside the callback.
602 {
603 auto const cb = [&](std::string const& writableName,
604 std::string const& archiveName) {
605 BEAST_EXPECT(writableName == "3");
606 BEAST_EXPECT(archiveName == "2");
607 // Ensure that dbr functions can be called from within the
608 // callback
609 BEAST_EXPECT(dbr->getName() == "3");
610 };
611 auto const cbReentrant = [&](std::string const& writableName,
612 std::string const& archiveName) {
613 BEAST_EXPECT(writableName == "2");
614 BEAST_EXPECT(archiveName == "1");
615 auto newBackend = makeBackendRotating(
616 env, scheduler, std::to_string(++threadNum));
617 // Reminder: doing this is stupid and should never happen
618 dbr->rotate(std::move(newBackend), cb);
619 };
620 auto newBackend = makeBackendRotating(
621 env, scheduler, std::to_string(++threadNum));
622 dbr->rotate(std::move(newBackend), cbReentrant);
623 }
624
625 BEAST_EXPECT(threadNum == 3);
626 BEAST_EXPECT(dbr->getName() == "3");
627 }
628
629 void
630 run() override
631 {
632 testClear();
635 testRotate();
636 }
637};
638
639// VFALCO This test fails because of thread asynchronous issues
640BEAST_DEFINE_TESTSUITE(SHAMapStore, app, ripple);
641
642} // namespace test
643} // namespace ripple
Represents a JSON value.
Definition json_value.h:130
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
virtual Config & config()=0
virtual SHAMapStore & getSHAMapStore()=0
virtual JobQueue & getJobQueue()=0
virtual RelationalDatabase & getRelationalDatabase()=0
virtual Logs & logs()=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:1097
beast::Journal journal(std::string const &name)
Definition Log.cpp:141
A NodeStore::Scheduler which uses the JobQueue.
virtual std::unique_ptr< Backend > make_Backend(Section const &parameters, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)=0
Create a backend.
static Manager & instance()
Returns the instance of the manager singleton.
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:19
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.
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.
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)
std::unique_ptr< NodeStore::Backend > makeBackendRotating(jtx::Env &env, NodeStoreScheduler &scheduler, std::string path)
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:102
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:103
Application & app()
Definition Env.h:242
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:772
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
Inject raw JSON.
Definition jtx_json.h:14
Add a path.
Definition paths.h:39
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.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr auto megabytes(T value) noexcept
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:53
@ rpcNOT_ENABLED
Definition ErrorCodes.h:40
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
static std::string nodeDatabase()
Information about the notional ledger backing the view.
NetClock::time_point closeTime
NetClock::duration closeTimeResolution
NetClock::time_point parentCloseTime
Set the sequence number on a JTx.
Definition seq.h:15
T time_since_epoch(T... args)
T to_string(T... args)
T value(T... args)