Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
CassandraBackendFamily.hpp
1#pragma once
2
3#include "data/BackendInterface.hpp"
4#include "data/DBHelpers.hpp"
5#include "data/LedgerCacheInterface.hpp"
6#include "data/LedgerHeaderCache.hpp"
7#include "data/Types.hpp"
8#include "data/cassandra/Concepts.hpp"
9#include "data/cassandra/Handle.hpp"
10#include "data/cassandra/Types.hpp"
11#include "data/cassandra/impl/ExecutionStrategy.hpp"
12#include "util/Assert.hpp"
13#include "util/LedgerUtils.hpp"
14#include "util/Profiler.hpp"
15#include "util/log/Logger.hpp"
16
17#include <boost/asio/spawn.hpp>
18#include <boost/json/object.hpp>
19#include <boost/uuid/string_generator.hpp>
20#include <boost/uuid/uuid.hpp>
21#include <cassandra.h>
22#include <fmt/format.h>
23#include <xrpl/basics/Blob.h>
24#include <xrpl/basics/base_uint.h>
25#include <xrpl/basics/strHex.h>
26#include <xrpl/protocol/AccountID.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/LedgerHeader.h>
29#include <xrpl/protocol/nft.h>
30
31#include <algorithm>
32#include <atomic>
33#include <chrono>
34#include <cstddef>
35#include <cstdint>
36#include <iterator>
37#include <limits>
38#include <optional>
39#include <stdexcept>
40#include <string>
41#include <tuple>
42#include <utility>
43#include <vector>
44
45class CacheBackendCassandraTest;
46
47namespace data::cassandra {
48
59template <
60 SomeSettingsProvider SettingsProviderType,
61 SomeExecutionStrategy ExecutionStrategyType,
62 typename SchemaType,
63 typename FetchLedgerCacheType = FetchLedgerCache>
65protected:
66 util::Logger log_{"Backend"};
67
68 SettingsProviderType settingsProvider_;
69 SchemaType schema_;
70 std::atomic_uint32_t ledgerSequence_ = 0u;
71 friend class ::CacheBackendCassandraTest;
72
73 Handle handle_;
74
75 // have to be mutable because BackendInterface constness :(
76 mutable ExecutionStrategyType executor_;
77 // TODO: move to interface level
78 mutable FetchLedgerCacheType ledgerCache_{};
79
80 static constexpr std::size_t kTransactionCursorBindIndex = 1;
81 static constexpr std::size_t kTransactionLimitBindIndex = 2;
82 static constexpr std::size_t kMPTokenIssuanceTxCursorBindIndex = 1;
83 static constexpr std::size_t kMPTokenIssuanceTxLimitBindIndex = 2;
84 static constexpr std::size_t kAccountMPTokenIssuanceTxCursorBindIndex = 2;
85 static constexpr std::size_t kAccountMPTokenIssuanceTxLimitBindIndex = 3;
86
87public:
96 SettingsProviderType settingsProvider,
98 bool readOnly
99 )
101 , settingsProvider_{std::move(settingsProvider)}
102 , schema_{settingsProvider_}
103 , handle_{settingsProvider_.getSettings()}
104 , executor_{settingsProvider_.getSettings(), handle_}
105 {
106 if (auto const res = handle_.connect(); not res.has_value())
107 throw std::runtime_error("Could not connect to database: " + res.error());
108
109 if (not readOnly) {
110 if (auto const res = handle_.execute(schema_.createKeyspace); not res.has_value()) {
111 // on datastax, creation of keyspaces can be configured to only be done thru the
112 // admin interface. this does not mean that the keyspace does not already exist tho.
113 if (res.error().code() != CASS_ERROR_SERVER_UNAUTHORIZED)
114 throw std::runtime_error("Could not create keyspace: " + res.error());
115 }
116
117 if (auto const res = handle_.executeEach(schema_.createSchema); not res.has_value())
118 throw std::runtime_error("Could not create schema: " + res.error());
119 }
120
121 try {
122 schema_.prepareStatements(handle_);
123 } catch (std::runtime_error const& ex) {
124 auto const error = fmt::format(
125 "Failed to prepare the statements: {}; readOnly: {}. ReadOnly should be turned off "
126 "or another Clio "
127 "node with write access to DB should be started first.",
128 ex.what(),
129 readOnly
130 );
131 LOG(log_.error()) << error;
132 throw std::runtime_error(error);
133 }
134 LOG(log_.info()) << "Created (revamped) CassandraBackend";
135 }
136
141
144 xrpl::AccountID const& account,
145 std::uint32_t const limit,
146 bool forward,
147 std::optional<TransactionsCursor> const& txnCursor,
148 boost::asio::yield_context yield
149 ) const override
150 {
151 auto rng = fetchLedgerRange();
152 if (!rng)
153 return {.txns = {}, .cursor = {}};
154
155 Statement const statement = [this, forward, &account]() {
156 if (forward)
157 return schema_->selectAccountTxForward.bind(account);
158
159 return schema_->selectAccountTx.bind(account);
160 }();
161
162 auto cursor = txnCursor;
163 if (cursor) {
164 statement.bindAt(kTransactionCursorBindIndex, cursor->asTuple());
165 LOG(log_.debug()) << "account = " << xrpl::strHex(account)
166 << " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
167 } else {
168 auto const seq = forward ? rng->minSequence : rng->maxSequence;
169 auto const placeHolder = forward ? 0u : std::numeric_limits<std::uint32_t>::max();
170
171 statement.bindAt(
172 kTransactionCursorBindIndex, std::make_tuple(placeHolder, placeHolder)
173 );
174 LOG(log_.debug()) << "account = " << xrpl::strHex(account) << " idx = " << seq
175 << " tuple = " << placeHolder;
176 }
177
178 // FIXME: Limit is a hack to support uint32_t properly for the time
179 // being. Should be removed later and schema updated to use proper
180 // types.
181 statement.bindAt(kTransactionLimitBindIndex, Limit{limit});
182 auto const res = executor_.read(yield, statement);
183 auto const& results = res.value();
184 if (not results.hasRows()) {
185 LOG(log_.debug()) << "No rows returned";
186 return {};
187 }
188
189 std::vector<xrpl::uint256> hashes = {};
190 auto numRows = results.numRows();
191 LOG(log_.info()) << "num_rows = " << numRows;
192
193 for (auto [hash, data] : extract<xrpl::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
194 hashes.push_back(hash);
195 if (--numRows == 0) {
196 LOG(log_.debug()) << "Setting cursor";
197 cursor = data;
198 }
199 }
200
201 auto const txns = fetchTransactions(hashes, yield);
202 LOG(log_.debug()) << "Txns = " << txns.size();
203
204 if (txns.size() == limit) {
205 LOG(log_.debug()) << "Returning cursor";
206 return {txns, cursor};
207 }
208
209 return {txns, {}};
210 }
211
212 void
214 {
215 executor_.sync();
216 }
217
218 void
219 writeLedger(xrpl::LedgerHeader const& ledgerHeader, std::string&& blob) override
220 {
221 executor_.write(schema_->insertLedgerHeader, ledgerHeader.seq, std::move(blob));
222
223 executor_.write(schema_->insertLedgerHash, ledgerHeader.hash, ledgerHeader.seq);
224
225 ledgerSequence_ = ledgerHeader.seq;
226 }
227
228 std::optional<std::uint32_t>
229 fetchLatestLedgerSequence(boost::asio::yield_context yield) const override
230 {
231 if (auto const res = executor_.read(yield, schema_->selectLatestLedger); res.has_value()) {
232 if (auto const& rows = *res; rows) {
233 if (auto const maybeRow = rows.template get<uint32_t>(); maybeRow.has_value())
234 return maybeRow;
235
236 LOG(log_.error()) << "Could not fetch latest ledger - no rows";
237 return std::nullopt;
238 }
239
240 LOG(log_.error()) << "Could not fetch latest ledger - no result";
241 } else {
242 LOG(log_.error()) << "Could not fetch latest ledger: " << res.error();
243 }
244
245 return std::nullopt;
246 }
247
248 std::optional<xrpl::LedgerHeader>
250 std::uint32_t const sequence,
251 boost::asio::yield_context yield
252 ) const override
253 {
254 if (auto const lock = ledgerCache_.get(); lock.has_value() && lock->seq == sequence)
255 return lock->ledger;
256
257 auto const res = executor_.read(yield, schema_->selectLedgerBySeq, sequence);
258 if (res) {
259 if (auto const& result = res.value(); result) {
260 if (auto const maybeValue = result.template get<std::vector<unsigned char>>();
261 maybeValue) {
262 auto const header = util::deserializeHeader(xrpl::makeSlice(*maybeValue));
263 ledgerCache_.put(FetchLedgerCache::CacheEntry{header, sequence});
264 return header;
265 }
266
267 LOG(log_.error()) << "Could not fetch ledger by sequence - no rows";
268 return std::nullopt;
269 }
270
271 LOG(log_.error()) << "Could not fetch ledger by sequence - no result";
272 } else {
273 LOG(log_.error()) << "Could not fetch ledger by sequence: " << res.error();
274 }
275
276 return std::nullopt;
277 }
278
279 std::optional<xrpl::LedgerHeader>
280 fetchLedgerByHash(xrpl::uint256 const& hash, boost::asio::yield_context yield) const override
281 {
282 if (auto const res = executor_.read(yield, schema_->selectLedgerByHash, hash); res) {
283 if (auto const& result = res.value(); result) {
284 if (auto const maybeValue = result.template get<uint32_t>(); maybeValue)
285 return fetchLedgerBySequence(*maybeValue, yield);
286
287 LOG(log_.error()) << "Could not fetch ledger by hash - no rows";
288 return std::nullopt;
289 }
290
291 LOG(log_.error()) << "Could not fetch ledger by hash - no result";
292 } else {
293 LOG(log_.error()) << "Could not fetch ledger by hash: " << res.error();
294 }
295
296 return std::nullopt;
297 }
298
299 std::optional<LedgerRange>
300 hardFetchLedgerRange(boost::asio::yield_context yield) const override
301 {
302 auto const res = executor_.read(yield, schema_->selectLedgerRange);
303 if (res) {
304 auto const& results = res.value();
305 if (not results.hasRows()) {
306 LOG(log_.debug()) << "Could not fetch ledger range - no rows";
307 return std::nullopt;
308 }
309
310 // TODO: this is probably a good place to use user type in
311 // cassandra instead of having two rows with bool flag. or maybe at
312 // least use tuple<int, int>?
313 LedgerRange range;
314 std::size_t idx = 0;
315 for (auto [seq] : extract<uint32_t>(results)) {
316 if (idx == 0) {
317 range.maxSequence = range.minSequence = seq;
318 } else if (idx == 1) {
319 range.maxSequence = seq;
320 }
321
322 ++idx;
323 }
324
325 if (range.minSequence > range.maxSequence)
326 std::swap(range.minSequence, range.maxSequence);
327
328 LOG(log_.debug()) << "After hardFetchLedgerRange range is " << range.minSequence << ":"
329 << range.maxSequence;
330 return range;
331 }
332 LOG(log_.error()) << "Could not fetch ledger range: " << res.error();
333
334 return std::nullopt;
335 }
336
337 std::vector<TransactionAndMetadata>
339 std::uint32_t const ledgerSequence,
340 boost::asio::yield_context yield
341 ) const override
342 {
343 auto hashes = fetchAllTransactionHashesInLedger(ledgerSequence, yield);
344 return fetchTransactions(hashes, yield);
345 }
346
347 std::vector<xrpl::uint256>
349 std::uint32_t const ledgerSequence,
350 boost::asio::yield_context yield
351 ) const override
352 {
353 auto start = std::chrono::system_clock::now();
354 auto const res =
355 executor_.read(yield, schema_->selectAllTransactionHashesInLedger, ledgerSequence);
356
357 if (not res) {
358 LOG(log_.error()) << "Could not fetch all transaction hashes: " << res.error();
359 return {};
360 }
361
362 auto const& result = res.value();
363 if (not result.hasRows()) {
364 LOG(log_.warn()) << "Could not fetch all transaction hashes - no rows; ledger = "
365 << std::to_string(ledgerSequence);
366 return {};
367 }
368
369 std::vector<xrpl::uint256> hashes;
370 for (auto [hash] : extract<xrpl::uint256>(result))
371 hashes.push_back(std::move(hash));
372
373 auto end = std::chrono::system_clock::now();
374 LOG(
375 log_.debug()
376 ) << "Fetched "
377 << hashes.size() << " transaction hashes from database in "
378 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
379 << " milliseconds";
380
381 return hashes;
382 }
383
384 std::optional<NFT>
386 xrpl::uint256 const& tokenID,
387 std::uint32_t const ledgerSequence,
388 boost::asio::yield_context yield
389 ) const override
390 {
391 auto const res = executor_.read(yield, schema_->selectNFT, tokenID, ledgerSequence);
392 if (not res)
393 return std::nullopt;
394
395 if (auto const maybeRow = res->template get<uint32_t, xrpl::AccountID, bool>(); maybeRow) {
396 auto [seq, owner, isBurned] = *maybeRow;
397 auto result = std::make_optional<NFT>(tokenID, seq, owner, isBurned);
398
399 // now fetch URI. Usually we will have the URI even for burned NFTs,
400 // but if the first ledger on this clio included NFTokenBurn
401 // transactions we will not have the URIs for any of those tokens.
402 // In any other case not having the URI indicates something went
403 // wrong with our data.
404 //
405 // TODO - in the future would be great for any handlers that use
406 // this could inject a warning in this case (the case of not having
407 // a URI because it was burned in the first ledger) to indicate that
408 // even though we are returning a blank URI, the NFT might have had
409 // one.
410 auto uriRes = executor_.read(yield, schema_->selectNFTURI, tokenID, ledgerSequence);
411 if (uriRes) {
412 if (auto const maybeUri = uriRes->template get<xrpl::Blob>(); maybeUri)
413 result->uri = *maybeUri;
414 }
415
416 return result;
417 }
418
419 LOG(log_.error()) << "Could not fetch NFT - no rows";
420 return std::nullopt;
421 }
422
425 xrpl::uint256 const& tokenID,
426 std::uint32_t const limit,
427 bool const forward,
428 std::optional<TransactionsCursor> const& cursorIn,
429 boost::asio::yield_context yield
430 ) const override
431 {
432 auto rng = fetchLedgerRange();
433 if (!rng)
434 return {.txns = {}, .cursor = {}};
435
436 Statement const statement = [this, forward, &tokenID]() {
437 if (forward)
438 return schema_->selectNFTTxForward.bind(tokenID);
439
440 return schema_->selectNFTTx.bind(tokenID);
441 }();
442
443 auto cursor = cursorIn;
444 if (cursor) {
445 statement.bindAt(kTransactionCursorBindIndex, cursor->asTuple());
446 LOG(log_.debug()) << "token_id = " << xrpl::strHex(tokenID)
447 << " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
448 } else {
449 auto const seq = forward ? rng->minSequence : rng->maxSequence;
450 auto const placeHolder = forward ? 0 : std::numeric_limits<std::uint32_t>::max();
451
452 statement.bindAt(
453 kTransactionCursorBindIndex, std::make_tuple(placeHolder, placeHolder)
454 );
455 LOG(log_.debug()) << "token_id = " << xrpl::strHex(tokenID) << " idx = " << seq
456 << " tuple = " << placeHolder;
457 }
458
459 statement.bindAt(kTransactionLimitBindIndex, Limit{limit});
460
461 auto const res = executor_.read(yield, statement);
462 auto const& results = res.value();
463 if (not results.hasRows()) {
464 LOG(log_.debug()) << "No rows returned";
465 return {};
466 }
467
468 std::vector<xrpl::uint256> hashes = {};
469 auto numRows = results.numRows();
470 LOG(log_.info()) << "num_rows = " << numRows;
471
472 for (auto [hash, data] : extract<xrpl::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
473 hashes.push_back(hash);
474 if (--numRows == 0) {
475 LOG(log_.debug()) << "Setting cursor";
476 cursor = data;
477
478 // forward queries by ledger/tx sequence `>=`
479 // so we have to advance the index by one
480 if (forward)
481 ++cursor->transactionIndex;
482 }
483 }
484
485 auto const txns = fetchTransactions(hashes, yield);
486 LOG(log_.debug()) << "NFT Txns = " << txns.size();
487
488 if (txns.size() == limit) {
489 LOG(log_.debug()) << "Returning cursor";
490 return {txns, cursor};
491 }
492
493 return {txns, {}};
494 }
495
498 xrpl::uint192 const& mptIssuanceID,
499 std::uint32_t const limit,
500 bool const forward,
501 std::optional<TransactionsCursor> const& cursorIn,
502 boost::asio::yield_context yield
503 ) const override
504 {
505 auto const statement = [this, forward, &mptIssuanceID]() {
506 if (forward)
507 return schema_->selectMPTokenIssuanceTxForward.bind(mptIssuanceID);
508
509 return schema_->selectMPTokenIssuanceTx.bind(mptIssuanceID);
510 }();
512 statement,
513 kMPTokenIssuanceTxCursorBindIndex,
514 kMPTokenIssuanceTxLimitBindIndex,
515 limit,
516 forward,
517 cursorIn,
518 yield
519 );
520 }
521
524 xrpl::uint192 const& mptIssuanceID,
525 xrpl::AccountID const& account,
526 std::uint32_t const limit,
527 bool const forward,
528 std::optional<TransactionsCursor> const& cursorIn,
529 boost::asio::yield_context yield
530 ) const override
531 {
532 auto const statement = [this, forward, &mptIssuanceID, &account]() {
533 if (forward)
534 return schema_->selectAccountMPTokenIssuanceTxForward.bind(mptIssuanceID, account);
535
536 return schema_->selectAccountMPTokenIssuanceTx.bind(mptIssuanceID, account);
537 }();
539 statement,
540 kAccountMPTokenIssuanceTxCursorBindIndex,
541 kAccountMPTokenIssuanceTxLimitBindIndex,
542 limit,
543 forward,
544 cursorIn,
545 yield
546 );
547 }
548
551 xrpl::uint192 const& mptID,
552 std::uint32_t const limit,
553 std::optional<xrpl::AccountID> const& cursorIn,
554 std::uint32_t const ledgerSequence,
555 boost::asio::yield_context yield
556 ) const override
557 {
558 auto const holderEntries = executor_.read(
559 yield,
560 schema_->selectMPTHolders,
561 mptID,
562 cursorIn.value_or(xrpl::AccountID(0)),
563 Limit{limit}
564 );
565
566 auto const& holderResults = holderEntries.value();
567 if (not holderResults.hasRows()) {
568 LOG(log_.debug()) << "No rows returned";
569 return {};
570 }
571
572 std::vector<xrpl::uint256> mptKeys;
573 std::optional<xrpl::AccountID> cursor;
574 for (auto const [holder] : extract<xrpl::AccountID>(holderResults)) {
575 mptKeys.push_back(xrpl::keylet::mptoken(mptID, holder).key);
576 cursor = holder;
577 }
578
579 auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield);
580
581 auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob const& mpt) {
582 return mpt.empty();
583 });
584
585 mptObjects.erase(it, mptObjects.end());
586
587 ASSERT(mptKeys.size() <= limit, "Number of keys can't exceed the limit");
588 if (mptKeys.size() == limit)
589 return {mptObjects, cursor};
590
591 return {mptObjects, {}};
592 }
593
594 std::optional<Blob>
596 xrpl::uint256 const& key,
597 std::uint32_t const sequence,
598 boost::asio::yield_context yield
599 ) const override
600 {
601 LOG(log_.debug()) << "Fetching ledger object for seq " << sequence
602 << ", key = " << xrpl::to_string(key);
603 if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
604 if (auto const result = res->template get<Blob>(); result) {
605 if (result->size())
606 return result;
607 } else {
608 LOG(log_.debug()) << "Could not fetch ledger object - no rows";
609 }
610 } else {
611 LOG(log_.error()) << "Could not fetch ledger object: " << res.error();
612 }
613
614 return std::nullopt;
615 }
616
617 std::optional<std::uint32_t>
619 xrpl::uint256 const& key,
620 std::uint32_t const sequence,
621 boost::asio::yield_context yield
622 ) const override
623 {
624 LOG(log_.debug()) << "Fetching ledger object for seq " << sequence
625 << ", key = " << xrpl::to_string(key);
626 if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
627 if (auto const result = res->template get<Blob, std::uint32_t>(); result) {
628 auto [_, seq] = *result;
629 return seq;
630 }
631 LOG(log_.debug()) << "Could not fetch ledger object sequence - no rows";
632 } else {
633 LOG(log_.error()) << "Could not fetch ledger object sequence: " << res.error();
634 }
635
636 return std::nullopt;
637 }
638
639 std::optional<TransactionAndMetadata>
640 fetchTransaction(xrpl::uint256 const& hash, boost::asio::yield_context yield) const override
641 {
642 if (auto const res = executor_.read(yield, schema_->selectTransaction, hash); res) {
643 if (auto const maybeValue = res->template get<Blob, Blob, uint32_t, uint32_t>();
644 maybeValue) {
645 auto [transaction, meta, seq, date] = *maybeValue;
646 return std::make_optional<TransactionAndMetadata>(transaction, meta, seq, date);
647 }
648
649 LOG(log_.debug()) << "Could not fetch transaction - no rows";
650 } else {
651 LOG(log_.error()) << "Could not fetch transaction: " << res.error();
652 }
653
654 return std::nullopt;
655 }
656
657 std::optional<xrpl::uint256>
659 xrpl::uint256 key,
660 std::uint32_t const ledgerSequence,
661 boost::asio::yield_context yield
662 ) const override
663 {
664 if (auto const res = executor_.read(yield, schema_->selectSuccessor, key, ledgerSequence);
665 res) {
666 if (auto const result = res->template get<xrpl::uint256>(); result) {
667 if (*result == kLastKey)
668 return std::nullopt;
669 return result;
670 }
671
672 LOG(log_.debug()) << "Could not fetch successor - no rows";
673 } else {
674 LOG(log_.error()) << "Could not fetch successor: " << res.error();
675 }
676
677 return std::nullopt;
678 }
679
680 std::vector<TransactionAndMetadata>
682 std::vector<xrpl::uint256> const& hashes,
683 boost::asio::yield_context yield
684 ) const override
685 {
686 if (hashes.empty())
687 return {};
688
689 auto const numHashes = hashes.size();
690 std::vector<TransactionAndMetadata> results;
691 results.reserve(numHashes);
692
693 std::vector<Statement> statements;
694 statements.reserve(numHashes);
695
696 auto const timeDiff = util::timed([this, yield, &results, &hashes, &statements]() {
697 // TODO: seems like a job for "hash IN (list of hashes)" instead?
698 std::transform(
699 std::cbegin(hashes),
700 std::cend(hashes),
701 std::back_inserter(statements),
702 [this](auto const& hash) { return schema_->selectTransaction.bind(hash); }
703 );
704
705 auto const entries = executor_.readEach(yield, statements);
706 std::transform(
707 std::cbegin(entries),
708 std::cend(entries),
709 std::back_inserter(results),
710 [](auto const& res) -> TransactionAndMetadata {
711 if (auto const maybeRow = res.template get<Blob, Blob, uint32_t, uint32_t>();
712 maybeRow)
713 return *maybeRow;
714
715 return {};
716 }
717 );
718 });
719
720 ASSERT(numHashes == results.size(), "Number of hashes and results must match");
721 LOG(log_.debug()) << "Fetched " << numHashes << " transactions from database in "
722 << timeDiff << " milliseconds";
723 return results;
724 }
725
726 std::vector<Blob>
728 std::vector<xrpl::uint256> const& keys,
729 std::uint32_t const sequence,
730 boost::asio::yield_context yield
731 ) const override
732 {
733 if (keys.empty())
734 return {};
735
736 auto const numKeys = keys.size();
737 LOG(log_.trace()) << "Fetching " << numKeys << " objects";
738
739 std::vector<Blob> results;
740 results.reserve(numKeys);
741
742 std::vector<Statement> statements;
743 statements.reserve(numKeys);
744
745 // TODO: seems like a job for "key IN (list of keys)" instead?
746 std::transform(
747 std::cbegin(keys),
748 std::cend(keys),
749 std::back_inserter(statements),
750 [this, &sequence](auto const& key) { return schema_->selectObject.bind(key, sequence); }
751 );
752
753 auto const entries = executor_.readEach(yield, statements);
754 std::transform(
755 std::cbegin(entries),
756 std::cend(entries),
757 std::back_inserter(results),
758 [](auto const& res) -> Blob {
759 if (auto const maybeValue = res.template get<Blob>(); maybeValue)
760 return *maybeValue;
761
762 return {};
763 }
764 );
765
766 LOG(log_.trace()) << "Fetched " << numKeys << " objects";
767 return results;
768 }
769
770 std::vector<LedgerObject>
772 std::uint32_t const ledgerSequence,
773 boost::asio::yield_context yield
774 ) const override
775 {
776 auto const [keys, timeDiff] =
777 util::timed([this, &ledgerSequence, yield]() -> std::vector<xrpl::uint256> {
778 auto const res = executor_.read(yield, schema_->selectDiff, ledgerSequence);
779 if (not res) {
780 LOG(log_.error()) << "Could not fetch ledger diff: " << res.error()
781 << "; ledger = " << ledgerSequence;
782 return {};
783 }
784
785 auto const& results = res.value();
786 if (not results) {
787 LOG(log_.error())
788 << "Could not fetch ledger diff - no rows; ledger = " << ledgerSequence;
789 return {};
790 }
791
792 std::vector<xrpl::uint256> resultKeys;
793 for (auto [key] : extract<xrpl::uint256>(results))
794 resultKeys.push_back(key);
795
796 return resultKeys;
797 });
798
799 // one of the above errors must have happened
800 if (keys.empty())
801 return {};
802
803 LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from database in "
804 << timeDiff << " milliseconds";
805
806 auto const objs = fetchLedgerObjects(keys, ledgerSequence, yield);
807 std::vector<LedgerObject> results;
808 results.reserve(keys.size());
809
810 std::transform(
811 std::cbegin(keys),
812 std::cend(keys),
813 std::cbegin(objs),
814 std::back_inserter(results),
815 [](auto const& key, auto const& obj) { return LedgerObject{key, obj}; }
816 );
817
818 return results;
819 }
820
821 std::optional<std::string>
823 std::string const& migratorName,
824 boost::asio::yield_context yield
825 ) const override
826 {
827 auto const res = executor_.read(yield, schema_->selectMigratorStatus, Text(migratorName));
828 if (not res) {
829 LOG(log_.error()) << "Could not fetch migrator status: " << res.error();
830 return {};
831 }
832
833 auto const& results = res.value();
834 if (not results) {
835 return {};
836 }
837
838 for (auto [statusString] : extract<std::string>(results))
839 return statusString;
840
841 return {};
842 }
843
844 std::expected<std::vector<std::pair<boost::uuids::uuid, std::string>>, std::string>
845 fetchClioNodesData(boost::asio::yield_context yield) const override
846 {
847 auto const readResult = executor_.read(yield, schema_->selectClioNodesData);
848 if (not readResult)
849 return std::unexpected{readResult.error().message()};
850
851 std::vector<std::pair<boost::uuids::uuid, std::string>> result;
852
853 for (auto [uuid, message] : extract<boost::uuids::uuid, std::string>(*readResult)) {
854 result.emplace_back(uuid, std::move(message));
855 }
856
857 return result;
858 }
859
860 void
861 doWriteLedgerObject(std::string&& key, std::uint32_t const seq, std::string&& blob) override
862 {
863 LOG(log_.trace()) << " Writing ledger object " << key.size() << ":" << seq << " ["
864 << blob.size() << " bytes]";
865
866 if (range_)
867 executor_.write(schema_->insertDiff, seq, key);
868
869 executor_.write(schema_->insertObject, std::move(key), seq, std::move(blob));
870 }
871
872 void
873 writeSuccessor(std::string&& key, std::uint32_t const seq, std::string&& successor) override
874 {
875 LOG(log_.trace()) << "Writing successor. key = " << key.size() << " bytes. "
876 << " seq = " << std::to_string(seq) << " successor = " << successor.size()
877 << " bytes.";
878 ASSERT(!key.empty(), "Key must not be empty");
879 ASSERT(!successor.empty(), "Successor must not be empty");
880
881 executor_.write(schema_->insertSuccessor, std::move(key), seq, std::move(successor));
882 }
883
884 void
885 writeAccountTransactions(std::vector<AccountTransactionsData> data) override
886 {
887 std::vector<Statement> statements;
888 statements.reserve(data.size() * 10); // assume 10 transactions avg
889
890 for (auto& record : data) {
891 std::ranges::transform(
892 record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
893 return schema_->insertAccountTx.bind(
894 std::forward<decltype(account)>(account),
895 std::make_tuple(record.ledgerSequence, record.transactionIndex),
896 record.txHash
897 );
898 }
899 );
900 }
901
902 executor_.write(std::move(statements));
903 }
904
905 void
907 {
908 std::vector<Statement> statements;
909 statements.reserve(record.accounts.size());
910
911 std::ranges::transform(
912 record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
913 return schema_->insertAccountTx.bind(
914 std::forward<decltype(account)>(account),
915 std::make_tuple(record.ledgerSequence, record.transactionIndex),
916 record.txHash
917 );
918 }
919 );
920
921 executor_.write(std::move(statements));
922 }
923
924 void
925 writeNFTTransactions(std::vector<NFTTransactionsData> const& data) override
926 {
927 std::vector<Statement> statements;
928 statements.reserve(data.size());
929
930 std::ranges::transform(data, std::back_inserter(statements), [this](auto const& record) {
931 return schema_->insertNFTTx.bind(
932 record.tokenID,
933 std::make_tuple(record.ledgerSequence, record.transactionIndex),
934 record.txHash
935 );
936 });
937
938 executor_.write(std::move(statements));
939 }
940
941 void
943 std::vector<MPTokenIssuanceTransactionsData> const& data
944 ) override
945 {
946 std::vector<Statement> statements;
947 statements.reserve(data.size());
948
949 std::ranges::transform(data, std::back_inserter(statements), [this](auto const& record) {
950 return schema_->insertMPTokenIssuanceTx.bind(
951 record.mptIssuanceID,
952 std::make_tuple(record.ledgerSequence, record.transactionIndex),
953 record.txHash
954 );
955 });
956
957 executor_.write(std::move(statements));
958 }
959
960 void
962 std::vector<MPTokenIssuanceTransactionsData> const& data
963 ) override
964 {
965 std::size_t numStatements = 0u;
966 for (auto const& record : data)
967 numStatements += record.accounts.size();
968
969 std::vector<Statement> statements;
970 statements.reserve(numStatements);
971
972 for (auto const& record : data) {
973 std::ranges::transform(
974 record.accounts,
975 std::back_inserter(statements),
976 [this, &record](auto const& account) {
977 return schema_->insertAccountMPTokenIssuanceTx.bind(
978 record.mptIssuanceID,
979 account,
980 std::make_tuple(record.ledgerSequence, record.transactionIndex),
981 record.txHash
982 );
983 }
984 );
985 }
986
987 executor_.write(std::move(statements));
988 }
989
990 void
992 std::string&& hash,
993 std::uint32_t const seq,
994 std::uint32_t const date,
995 std::string&& transaction,
996 std::string&& metadata
997 ) override
998 {
999 LOG(log_.trace()) << "Writing txn to database";
1000
1001 executor_.write(schema_->insertLedgerTransaction, seq, hash);
1002 executor_.write(
1003 schema_->insertTransaction,
1004 std::move(hash),
1005 seq,
1006 date,
1007 std::move(transaction),
1008 std::move(metadata)
1009 );
1010 }
1011
1012 void
1013 writeNFTs(std::vector<NFTsData> const& data) override
1014 {
1015 std::vector<Statement> statements;
1016 statements.reserve(data.size() * 3);
1017
1018 for (NFTsData const& record : data) {
1019 if (!record.onlyUriChanged) {
1020 statements.push_back(schema_->insertNFT.bind(
1021 record.tokenID, record.ledgerSequence, record.owner, record.isBurned
1022 ));
1023
1024 // If `uri` is set (and it can be set to an empty uri), we know this
1025 // is a net-new NFT. That is, this NFT has not been seen before by
1026 // us _OR_ it is in the extreme edge case of a re-minted NFT ID with
1027 // the same NFT ID as an already-burned token. In this case, we need
1028 // to record the URI and link to the issuer_nf_tokens table.
1029 if (record.uri) {
1030 statements.push_back(schema_->insertIssuerNFT.bind(
1031 xrpl::nft::getIssuer(record.tokenID),
1032 static_cast<uint32_t>(xrpl::nft::getTaxon(record.tokenID)),
1033 record.tokenID
1034 ));
1035 statements.push_back(schema_->insertNFTURI.bind(
1036 record.tokenID, record.ledgerSequence, *record.uri
1037 ));
1038 }
1039 } else {
1040 // only uri changed, we update the uri table only
1041 statements.push_back(
1042 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1043 schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, *record.uri)
1044 );
1045 }
1046 }
1047
1048 executor_.writeEach(std::move(statements));
1049 }
1050
1051 void
1052 writeMPTHolders(std::vector<MPTHolderData> const& data) override
1053 {
1054 std::vector<Statement> statements;
1055 statements.reserve(data.size());
1056 for (auto [mptId, holder] : data)
1057 statements.push_back(schema_->insertMPTHolder.bind(mptId, holder));
1058
1059 executor_.write(std::move(statements));
1060 }
1061
1062 void
1063 startWrites() const override
1064 {
1065 // Note: no-op in original implementation too.
1066 // probably was used in PG to start a transaction or smth.
1067 }
1068
1069 void
1070 writeMigratorStatus(std::string const& migratorName, std::string const& status) override
1071 {
1072 executor_.writeSync(
1073 schema_->insertMigratorStatus,
1074 data::cassandra::Text{migratorName},
1075 data::cassandra::Text(status)
1076 );
1077 }
1078
1079 void
1080 writeNodeMessage(boost::uuids::uuid const& uuid, std::string message) override
1081 {
1082 executor_.writeSync(
1083 schema_->updateClioNodeMessage, data::cassandra::Text{std::move(message)}, uuid
1084 );
1085 }
1086
1087 bool
1088 isTooBusy() const override
1089 {
1090 return executor_.isTooBusy();
1091 }
1092
1093 boost::json::object
1094 stats() const override
1095 {
1096 return executor_.stats();
1097 }
1098
1099protected:
1106 bool
1107 executeSyncUpdate(Statement statement)
1108 {
1109 auto const res = executor_.writeSync(statement);
1110 auto maybeSuccess = res->template get<bool>();
1111 if (not maybeSuccess) {
1112 LOG(log_.error()) << "executeSyncUpdate - error getting result - no row";
1113 return false;
1114 }
1115
1116 if (not *maybeSuccess) {
1117 LOG(log_.warn()) << "Update failed. Checking if DB state is what we expect";
1118
1119 // error may indicate that another writer wrote something.
1120 // in this case let's just compare the current state of things
1121 // against what we were trying to write in the first place and
1122 // use that as the source of truth for the result.
1123 auto rng = hardFetchLedgerRangeNoThrow();
1124 return rng && rng->maxSequence == ledgerSequence_;
1125 }
1126
1127 return true;
1128 }
1129
1148 Statement const& statement,
1149 std::size_t const cursorIdx,
1150 std::size_t const limitIdx,
1151 std::uint32_t const limit,
1152 bool const forward,
1153 std::optional<TransactionsCursor> const& cursorIn,
1154 boost::asio::yield_context yield
1155 ) const
1156 {
1157 auto rng = fetchLedgerRange();
1158 if (!rng)
1159 return {.txns = {}, .cursor = {}};
1160
1161 auto cursor = cursorIn;
1162 if (cursor.has_value()) {
1163 statement.bindAt(cursorIdx, cursor->asTuple());
1164 } else {
1165 // Forward uses the nft_history-style inclusive lower bound; reverse starts just past
1166 // the latest validated ledger so its exclusive `<` query includes that ledger's rows.
1167 auto const ledgerSequence = forward ? rng->minSequence : rng->maxSequence;
1168 auto const transactionIndex = forward ? 0u : std::numeric_limits<std::uint32_t>::max();
1169 statement.bindAt(cursorIdx, std::make_tuple(ledgerSequence, transactionIndex));
1170 }
1171
1172 statement.bindAt(limitIdx, Limit{limit});
1173
1174 auto const res = executor_.read(yield, statement);
1175 auto const& results = res.value();
1176 if (not results.hasRows()) {
1177 LOG(log_.debug()) << "No rows returned";
1178 return {};
1179 }
1180
1181 std::vector<xrpl::uint256> hashes = {};
1182 auto numRows = results.numRows();
1183
1184 for (auto const& [hash, data] :
1185 extract<xrpl::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
1186 hashes.push_back(hash);
1187
1188 if (--numRows == 0) {
1189 LOG(log_.debug()) << "Setting cursor";
1190 cursor = data;
1191
1192 // forward queries by ledger/tx sequence `>=`
1193 // so we have to advance the index by one
1194 if (forward)
1195 ++cursor->transactionIndex;
1196 }
1197 }
1198
1199 auto txns = fetchTransactions(hashes, yield);
1200 LOG(log_.debug()) << "MPTokenIssuance Txns = " << txns.size();
1201
1202 if (txns.size() == limit) {
1203 LOG(log_.debug()) << "Returning cursor";
1204 return {std::move(txns), cursor};
1205 }
1206
1207 return {std::move(txns), {}};
1208 }
1209};
1210
1211} // namespace data::cassandra
BackendInterface(LedgerCacheInterface &cache)
Construct a new backend interface instance.
Definition BackendInterface.hpp:139
std::optional< LedgerRange > hardFetchLedgerRangeNoThrow() const
Fetches the ledger range from DB retrying until no DatabaseTimeout is thrown.
Definition BackendInterface.cpp:53
std::optional< LedgerRange > fetchLedgerRange() const
Fetch the current ledger range.
Definition BackendInterface.cpp:251
std::vector< Blob > fetchLedgerObjects(std::vector< xrpl::uint256 > const &keys, std::uint32_t sequence, boost::asio::yield_context yield) const
Fetches all ledger objects by their keys.
Definition BackendInterface.cpp:95
LedgerCacheInterface const & cache() const
Definition BackendInterface.hpp:151
A simple cache holding one xrpl::LedgerHeader to reduce DB lookups.
Definition LedgerHeaderCache.hpp:22
Cache for an entire ledger.
Definition LedgerCacheInterface.hpp:21
void writeMigratorStatus(std::string const &migratorName, std::string const &status) override
Mark the migration status of a migrator as Migrated in the database.
Definition CassandraBackendFamily.hpp:1070
std::optional< LedgerRange > hardFetchLedgerRange(boost::asio::yield_context yield) const override
Fetches the ledger range from DB.
Definition CassandraBackendFamily.hpp:300
std::optional< xrpl::LedgerHeader > fetchLedgerBySequence(std::uint32_t const sequence, boost::asio::yield_context yield) const override
Fetches a specific ledger by sequence number.
Definition CassandraBackendFamily.hpp:249
TransactionsAndCursor fetchAccountMPTokenIssuanceTransactions(xrpl::uint192 const &mptIssuanceID, xrpl::AccountID const &account, std::uint32_t const limit, bool const forward, std::optional< TransactionsCursor > const &cursorIn, boost::asio::yield_context yield) const override
Fetches transactions for a particular MPTokenIssuance ID involving a particular account.
Definition CassandraBackendFamily.hpp:523
std::optional< NFT > fetchNFT(xrpl::uint256 const &tokenID, std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
Fetches a specific NFT.
Definition CassandraBackendFamily.hpp:385
void startWrites() const override
Starts a write transaction with the DB. No-op for cassandra.
Definition CassandraBackendFamily.hpp:1063
void doWriteLedgerObject(std::string &&key, std::uint32_t const seq, std::string &&blob) override
Writes a ledger object to the database.
Definition CassandraBackendFamily.hpp:861
std::optional< std::uint32_t > doFetchLedgerObjectSeq(xrpl::uint256 const &key, std::uint32_t const sequence, boost::asio::yield_context yield) const override
The database-specific implementation for fetching a ledger object sequence.
Definition CassandraBackendFamily.hpp:618
std::optional< xrpl::LedgerHeader > fetchLedgerByHash(xrpl::uint256 const &hash, boost::asio::yield_context yield) const override
Fetches a specific ledger by hash.
Definition CassandraBackendFamily.hpp:280
TransactionsAndCursor fetchAccountTransactions(xrpl::AccountID const &account, std::uint32_t const limit, bool forward, std::optional< TransactionsCursor > const &txnCursor, boost::asio::yield_context yield) const override
Fetches all transactions for a specific account.
Definition CassandraBackendFamily.hpp:143
std::optional< Blob > doFetchLedgerObject(xrpl::uint256 const &key, std::uint32_t const sequence, boost::asio::yield_context yield) const override
The database-specific implementation for fetching a ledger object.
Definition CassandraBackendFamily.hpp:595
void writeNFTs(std::vector< NFTsData > const &data) override
Writes NFTs to the database.
Definition CassandraBackendFamily.hpp:1013
void writeNFTTransactions(std::vector< NFTTransactionsData > const &data) override
Write NFTs transactions.
Definition CassandraBackendFamily.hpp:925
void writeNodeMessage(boost::uuids::uuid const &uuid, std::string message) override
Write a node message. Used by ClusterCommunicationService.
Definition CassandraBackendFamily.hpp:1080
std::optional< std::uint32_t > fetchLatestLedgerSequence(boost::asio::yield_context yield) const override
Fetches the latest ledger sequence.
Definition CassandraBackendFamily.hpp:229
CassandraBackendFamily(SettingsProviderType settingsProvider, data::LedgerCacheInterface &cache, bool readOnly)
Create a new cassandra/scylla backend instance.
Definition CassandraBackendFamily.hpp:95
void writeAccountMPTokenIssuanceTransactions(std::vector< MPTokenIssuanceTransactionsData > const &data) override
Write MPTokenIssuance transaction index rows to the account_mptoken_issuance_transactions table.
Definition CassandraBackendFamily.hpp:961
MPTHoldersAndCursor fetchMPTHolders(xrpl::uint192 const &mptID, std::uint32_t const limit, std::optional< xrpl::AccountID > const &cursorIn, std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
Fetches all holders' balances for a MPTIssuanceID.
Definition CassandraBackendFamily.hpp:550
std::optional< xrpl::uint256 > doFetchSuccessorKey(xrpl::uint256 key, std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
Database-specific implementation of fetching the successor key.
Definition CassandraBackendFamily.hpp:658
TransactionsAndCursor fetchNFTTransactions(xrpl::uint256 const &tokenID, std::uint32_t const limit, bool const forward, std::optional< TransactionsCursor > const &cursorIn, boost::asio::yield_context yield) const override
Fetches all transactions for a specific NFT.
Definition CassandraBackendFamily.hpp:424
bool isTooBusy() const override
Definition CassandraBackendFamily.hpp:1088
void writeMPTHolders(std::vector< MPTHolderData > const &data) override
Write accounts that started holding onto a MPT.
Definition CassandraBackendFamily.hpp:1052
void writeAccountTransaction(AccountTransactionsData record) override
Write a new account transaction.
Definition CassandraBackendFamily.hpp:906
void writeSuccessor(std::string &&key, std::uint32_t const seq, std::string &&successor) override
Write a new successor.
Definition CassandraBackendFamily.hpp:873
void waitForWritesToFinish() override
Wait for all pending writes to finish.
Definition CassandraBackendFamily.hpp:213
TransactionsAndCursor fetchMPTokenIssuanceTransactions(xrpl::uint192 const &mptIssuanceID, std::uint32_t const limit, bool const forward, std::optional< TransactionsCursor > const &cursorIn, boost::asio::yield_context yield) const override
Fetches transactions for a particular MPTokenIssuance ID.
Definition CassandraBackendFamily.hpp:497
void writeLedger(xrpl::LedgerHeader const &ledgerHeader, std::string &&blob) override
Writes to a specific ledger.
Definition CassandraBackendFamily.hpp:219
std::optional< TransactionAndMetadata > fetchTransaction(xrpl::uint256 const &hash, boost::asio::yield_context yield) const override
Fetches a specific transaction.
Definition CassandraBackendFamily.hpp:640
TransactionsAndCursor fetchMPTokenIssuanceTransactionsImpl(Statement const &statement, std::size_t const cursorIdx, std::size_t const limitIdx, std::uint32_t const limit, bool const forward, std::optional< TransactionsCursor > const &cursorIn, boost::asio::yield_context yield) const
Shared implementation of the two MPTokenIssuance transaction-index fetchers.
Definition CassandraBackendFamily.hpp:1147
boost::json::object stats() const override
Definition CassandraBackendFamily.hpp:1094
std::optional< std::string > fetchMigratorStatus(std::string const &migratorName, boost::asio::yield_context yield) const override
Fetches the status of migrator by name.
Definition CassandraBackendFamily.hpp:822
std::expected< std::vector< std::pair< boost::uuids::uuid, std::string > >, std::string > fetchClioNodesData(boost::asio::yield_context yield) const override
Fetches the data of all nodes in the cluster.
Definition CassandraBackendFamily.hpp:845
void writeAccountTransactions(std::vector< AccountTransactionsData > data) override
Write a new set of account transactions.
Definition CassandraBackendFamily.hpp:885
std::vector< Blob > doFetchLedgerObjects(std::vector< xrpl::uint256 > const &keys, std::uint32_t const sequence, boost::asio::yield_context yield) const override
The database-specific implementation for fetching ledger objects.
Definition CassandraBackendFamily.hpp:727
CassandraBackendFamily(CassandraBackendFamily &&)=delete
Move constructor is deleted because handle_ is shared by reference with executor.
bool executeSyncUpdate(Statement statement)
Executes statements and tries to write to DB.
Definition CassandraBackendFamily.hpp:1107
void writeMPTokenIssuanceTransactions(std::vector< MPTokenIssuanceTransactionsData > const &data) override
Write MPTokenIssuance transaction index rows to the mptoken_issuance_transactions table.
Definition CassandraBackendFamily.hpp:942
void writeTransaction(std::string &&hash, std::uint32_t const seq, std::uint32_t const date, std::string &&transaction, std::string &&metadata) override
Writes a new transaction.
Definition CassandraBackendFamily.hpp:991
std::vector< xrpl::uint256 > fetchAllTransactionHashesInLedger(std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
Fetches all transaction hashes from a specific ledger.
Definition CassandraBackendFamily.hpp:348
std::vector< TransactionAndMetadata > fetchTransactions(std::vector< xrpl::uint256 > const &hashes, boost::asio::yield_context yield) const override
Fetches multiple transactions.
Definition CassandraBackendFamily.hpp:681
std::vector< LedgerObject > fetchLedgerDiff(std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
Returns the difference between ledgers.
Definition CassandraBackendFamily.hpp:771
std::vector< TransactionAndMetadata > fetchAllTransactionsInLedger(std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
Fetches all transactions from a specific ledger.
Definition CassandraBackendFamily.hpp:338
Represents a handle to the cassandra database cluster.
Definition Handle.hpp:27
void bindAt(std::size_t const idx, Type &&value) const
Binds an argument to a specific index.
Definition Statement.hpp:76
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:78
The requirements of an execution strategy.
Definition Concepts.hpp:35
The requirements of a settings provider.
Definition Concepts.hpp:24
This namespace implements a wrapper for the Cassandra C++ driver.
Definition CassandraBackendFamily.hpp:47
impl::ResultExtractor< Types... > extract(Handle::ResultType const &result)
Extracts the results into series of std::tuple<Types...> by creating a simple wrapper with an STL inp...
Definition Handle.hpp:314
This namespace implements the data access layer and related components.
Definition AmendmentCenter.cpp:56
xrpl::LedgerHeader deserializeHeader(xrpl::Slice data)
Deserializes a xrpl::LedgerHeader from xrpl::Slice of data.
Definition LedgerUtils.hpp:240
auto timed(FnType &&func)
Profiler function to measure the time a function execution consumes.
Definition Profiler.hpp:21
Struct used to keep track of what to write to account_transactions/account_tx tables.
Definition DBHelpers.hpp:26
Represents an NFT state at a particular ledger.
Definition DBHelpers.hpp:93
Struct to store ledger header cache entry and the sequence it belongs to.
Definition LedgerHeaderCache.hpp:29
Represents an object in the ledger.
Definition Types.hpp:22
Stores a range of sequences as a min and max pair.
Definition Types.hpp:243
Represents an array of MPTokens.
Definition Types.hpp:235
Represents a transaction and its metadata bundled together.
Definition Types.hpp:49
Represests a bundle of transactions with metadata and a cursor to the next page.
Definition Types.hpp:153
A strong type wrapper for int32_t.
Definition Types.hpp:38
A strong type wrapper for string.
Definition Types.hpp:49