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
145 [[nodiscard]] std::chrono::milliseconds
146 initialRetryDelay() const override
147 {
148 return settingsProvider_.getInitialRetryDelay();
149 }
150
154 [[nodiscard]] std::chrono::milliseconds
155 maxRetryDelay() const override
156 {
157 return settingsProvider_.getMaxRetryDelay();
158 }
159
162 xrpl::AccountID const& account,
163 std::uint32_t const limit,
164 bool forward,
165 std::optional<TransactionsCursor> const& txnCursor,
166 boost::asio::yield_context yield
167 ) const override
168 {
169 auto rng = fetchLedgerRange();
170 if (!rng)
171 return {.txns = {}, .cursor = {}};
172
173 Statement const statement = [this, forward, &account]() {
174 if (forward)
175 return schema_->selectAccountTxForward.bind(account);
176
177 return schema_->selectAccountTx.bind(account);
178 }();
179
180 auto cursor = txnCursor;
181 if (cursor) {
182 statement.bindAt(kTransactionCursorBindIndex, cursor->asTuple());
183 LOG(log_.debug()) << "account = " << xrpl::strHex(account)
184 << " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
185 } else {
186 auto const seq = forward ? rng->minSequence : rng->maxSequence;
187 auto const placeHolder = forward ? 0u : std::numeric_limits<std::uint32_t>::max();
188
189 statement.bindAt(
190 kTransactionCursorBindIndex, std::make_tuple(placeHolder, placeHolder)
191 );
192 LOG(log_.debug()) << "account = " << xrpl::strHex(account) << " idx = " << seq
193 << " tuple = " << placeHolder;
194 }
195
196 // FIXME: Limit is a hack to support uint32_t properly for the time
197 // being. Should be removed later and schema updated to use proper
198 // types.
199 statement.bindAt(kTransactionLimitBindIndex, Limit{limit});
200 auto const res = executor_.read(yield, statement);
201 auto const& results = res.value();
202 if (not results.hasRows()) {
203 LOG(log_.debug()) << "No rows returned";
204 return {};
205 }
206
207 std::vector<xrpl::uint256> hashes = {};
208 auto numRows = results.numRows();
209 LOG(log_.info()) << "num_rows = " << numRows;
210
211 for (auto [hash, data] : extract<xrpl::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
212 hashes.push_back(hash);
213 if (--numRows == 0) {
214 LOG(log_.debug()) << "Setting cursor";
215 cursor = data;
216 }
217 }
218
219 auto const txns = fetchTransactions(hashes, yield);
220 LOG(log_.debug()) << "Txns = " << txns.size();
221
222 if (txns.size() == limit) {
223 LOG(log_.debug()) << "Returning cursor";
224 return {txns, cursor};
225 }
226
227 return {txns, {}};
228 }
229
230 void
232 {
233 executor_.sync();
234 }
235
236 void
237 writeLedger(xrpl::LedgerHeader const& ledgerHeader, std::string&& blob) override
238 {
239 executor_.write(schema_->insertLedgerHeader, ledgerHeader.seq, std::move(blob));
240
241 executor_.write(schema_->insertLedgerHash, ledgerHeader.hash, ledgerHeader.seq);
242
243 ledgerSequence_ = ledgerHeader.seq;
244 }
245
246 std::optional<std::uint32_t>
247 fetchLatestLedgerSequence(boost::asio::yield_context yield) const override
248 {
249 if (auto const res = executor_.read(yield, schema_->selectLatestLedger); res.has_value()) {
250 if (auto const& rows = *res; rows) {
251 if (auto const maybeRow = rows.template get<uint32_t>(); maybeRow.has_value())
252 return maybeRow;
253
254 LOG(log_.error()) << "Could not fetch latest ledger - no rows";
255 return std::nullopt;
256 }
257
258 LOG(log_.error()) << "Could not fetch latest ledger - no result";
259 } else {
260 LOG(log_.error()) << "Could not fetch latest ledger: " << res.error();
261 }
262
263 return std::nullopt;
264 }
265
266 std::optional<xrpl::LedgerHeader>
268 std::uint32_t const sequence,
269 boost::asio::yield_context yield
270 ) const override
271 {
272 if (auto const lock = ledgerCache_.get(); lock.has_value() && lock->seq == sequence)
273 return lock->ledger;
274
275 auto const res = executor_.read(yield, schema_->selectLedgerBySeq, sequence);
276 if (res) {
277 if (auto const& result = res.value(); result) {
278 if (auto const maybeValue = result.template get<std::vector<unsigned char>>();
279 maybeValue) {
280 auto const header = util::deserializeHeader(xrpl::makeSlice(*maybeValue));
281 ledgerCache_.put(FetchLedgerCache::CacheEntry{header, sequence});
282 return header;
283 }
284
285 LOG(log_.error()) << "Could not fetch ledger by sequence - no rows";
286 return std::nullopt;
287 }
288
289 LOG(log_.error()) << "Could not fetch ledger by sequence - no result";
290 } else {
291 LOG(log_.error()) << "Could not fetch ledger by sequence: " << res.error();
292 }
293
294 return std::nullopt;
295 }
296
297 std::optional<xrpl::LedgerHeader>
298 fetchLedgerByHash(xrpl::uint256 const& hash, boost::asio::yield_context yield) const override
299 {
300 if (auto const res = executor_.read(yield, schema_->selectLedgerByHash, hash); res) {
301 if (auto const& result = res.value(); result) {
302 if (auto const maybeValue = result.template get<uint32_t>(); maybeValue)
303 return fetchLedgerBySequence(*maybeValue, yield);
304
305 LOG(log_.error()) << "Could not fetch ledger by hash - no rows";
306 return std::nullopt;
307 }
308
309 LOG(log_.error()) << "Could not fetch ledger by hash - no result";
310 } else {
311 LOG(log_.error()) << "Could not fetch ledger by hash: " << res.error();
312 }
313
314 return std::nullopt;
315 }
316
317 std::optional<LedgerRange>
318 hardFetchLedgerRange(boost::asio::yield_context yield) const override
319 {
320 auto const res = executor_.read(yield, schema_->selectLedgerRange);
321 if (res) {
322 auto const& results = res.value();
323 if (not results.hasRows()) {
324 LOG(log_.debug()) << "Could not fetch ledger range - no rows";
325 return std::nullopt;
326 }
327
328 // TODO: this is probably a good place to use user type in
329 // cassandra instead of having two rows with bool flag. or maybe at
330 // least use tuple<int, int>?
331 LedgerRange range;
332 std::size_t idx = 0;
333 for (auto [seq] : extract<uint32_t>(results)) {
334 if (idx == 0) {
335 range.maxSequence = range.minSequence = seq;
336 } else if (idx == 1) {
337 range.maxSequence = seq;
338 }
339
340 ++idx;
341 }
342
343 if (range.minSequence > range.maxSequence)
344 std::swap(range.minSequence, range.maxSequence);
345
346 LOG(log_.debug()) << "After hardFetchLedgerRange range is " << range.minSequence << ":"
347 << range.maxSequence;
348 return range;
349 }
350 LOG(log_.error()) << "Could not fetch ledger range: " << res.error();
351
352 return std::nullopt;
353 }
354
355 std::vector<TransactionAndMetadata>
357 std::uint32_t const ledgerSequence,
358 boost::asio::yield_context yield
359 ) const override
360 {
361 auto hashes = fetchAllTransactionHashesInLedger(ledgerSequence, yield);
362 return fetchTransactions(hashes, yield);
363 }
364
365 std::vector<xrpl::uint256>
367 std::uint32_t const ledgerSequence,
368 boost::asio::yield_context yield
369 ) const override
370 {
371 auto start = std::chrono::system_clock::now();
372 auto const res =
373 executor_.read(yield, schema_->selectAllTransactionHashesInLedger, ledgerSequence);
374
375 if (not res) {
376 LOG(log_.error()) << "Could not fetch all transaction hashes: " << res.error();
377 return {};
378 }
379
380 auto const& result = res.value();
381 if (not result.hasRows()) {
382 LOG(log_.warn()) << "Could not fetch all transaction hashes - no rows; ledger = "
383 << std::to_string(ledgerSequence);
384 return {};
385 }
386
387 std::vector<xrpl::uint256> hashes;
388 for (auto [hash] : extract<xrpl::uint256>(result))
389 hashes.push_back(std::move(hash));
390
391 auto end = std::chrono::system_clock::now();
392 LOG(
393 log_.debug()
394 ) << "Fetched "
395 << hashes.size() << " transaction hashes from database in "
396 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
397 << " milliseconds";
398
399 return hashes;
400 }
401
402 std::optional<NFT>
404 xrpl::uint256 const& tokenID,
405 std::uint32_t const ledgerSequence,
406 boost::asio::yield_context yield
407 ) const override
408 {
409 auto const res = executor_.read(yield, schema_->selectNFT, tokenID, ledgerSequence);
410 if (not res)
411 return std::nullopt;
412
413 if (auto const maybeRow = res->template get<uint32_t, xrpl::AccountID, bool>(); maybeRow) {
414 auto [seq, owner, isBurned] = *maybeRow;
415 auto result = std::make_optional<NFT>(tokenID, seq, owner, isBurned);
416
417 // now fetch URI. Usually we will have the URI even for burned NFTs,
418 // but if the first ledger on this clio included NFTokenBurn
419 // transactions we will not have the URIs for any of those tokens.
420 // In any other case not having the URI indicates something went
421 // wrong with our data.
422 //
423 // TODO - in the future would be great for any handlers that use
424 // this could inject a warning in this case (the case of not having
425 // a URI because it was burned in the first ledger) to indicate that
426 // even though we are returning a blank URI, the NFT might have had
427 // one.
428 auto uriRes = executor_.read(yield, schema_->selectNFTURI, tokenID, ledgerSequence);
429 if (uriRes) {
430 if (auto const maybeUri = uriRes->template get<xrpl::Blob>(); maybeUri)
431 result->uri = *maybeUri;
432 }
433
434 return result;
435 }
436
437 LOG(log_.error()) << "Could not fetch NFT - no rows";
438 return std::nullopt;
439 }
440
443 xrpl::uint256 const& tokenID,
444 std::uint32_t const limit,
445 bool const forward,
446 std::optional<TransactionsCursor> const& cursorIn,
447 boost::asio::yield_context yield
448 ) const override
449 {
450 auto rng = fetchLedgerRange();
451 if (!rng)
452 return {.txns = {}, .cursor = {}};
453
454 Statement const statement = [this, forward, &tokenID]() {
455 if (forward)
456 return schema_->selectNFTTxForward.bind(tokenID);
457
458 return schema_->selectNFTTx.bind(tokenID);
459 }();
460
461 auto cursor = cursorIn;
462 if (cursor) {
463 statement.bindAt(kTransactionCursorBindIndex, cursor->asTuple());
464 LOG(log_.debug()) << "token_id = " << xrpl::strHex(tokenID)
465 << " tuple = " << cursor->ledgerSequence << cursor->transactionIndex;
466 } else {
467 auto const seq = forward ? rng->minSequence : rng->maxSequence;
468 auto const placeHolder = forward ? 0 : std::numeric_limits<std::uint32_t>::max();
469
470 statement.bindAt(
471 kTransactionCursorBindIndex, std::make_tuple(placeHolder, placeHolder)
472 );
473 LOG(log_.debug()) << "token_id = " << xrpl::strHex(tokenID) << " idx = " << seq
474 << " tuple = " << placeHolder;
475 }
476
477 statement.bindAt(kTransactionLimitBindIndex, Limit{limit});
478
479 auto const res = executor_.read(yield, statement);
480 auto const& results = res.value();
481 if (not results.hasRows()) {
482 LOG(log_.debug()) << "No rows returned";
483 return {};
484 }
485
486 std::vector<xrpl::uint256> hashes = {};
487 auto numRows = results.numRows();
488 LOG(log_.info()) << "num_rows = " << numRows;
489
490 for (auto [hash, data] : extract<xrpl::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
491 hashes.push_back(hash);
492 if (--numRows == 0) {
493 LOG(log_.debug()) << "Setting cursor";
494 cursor = data;
495
496 // forward queries by ledger/tx sequence `>=`
497 // so we have to advance the index by one
498 if (forward)
499 ++cursor->transactionIndex;
500 }
501 }
502
503 auto const txns = fetchTransactions(hashes, yield);
504 LOG(log_.debug()) << "NFT Txns = " << txns.size();
505
506 if (txns.size() == limit) {
507 LOG(log_.debug()) << "Returning cursor";
508 return {txns, cursor};
509 }
510
511 return {txns, {}};
512 }
513
516 xrpl::uint192 const& mptIssuanceID,
517 std::uint32_t const limit,
518 bool const forward,
519 std::optional<TransactionsCursor> const& cursorIn,
520 boost::asio::yield_context yield
521 ) const override
522 {
523 auto const statement = [this, forward, &mptIssuanceID]() {
524 if (forward)
525 return schema_->selectMPTokenIssuanceTxForward.bind(mptIssuanceID);
526
527 return schema_->selectMPTokenIssuanceTx.bind(mptIssuanceID);
528 }();
530 statement,
531 kMPTokenIssuanceTxCursorBindIndex,
532 kMPTokenIssuanceTxLimitBindIndex,
533 limit,
534 forward,
535 cursorIn,
536 yield
537 );
538 }
539
542 xrpl::uint192 const& mptIssuanceID,
543 xrpl::AccountID const& account,
544 std::uint32_t const limit,
545 bool const forward,
546 std::optional<TransactionsCursor> const& cursorIn,
547 boost::asio::yield_context yield
548 ) const override
549 {
550 auto const statement = [this, forward, &mptIssuanceID, &account]() {
551 if (forward)
552 return schema_->selectAccountMPTokenIssuanceTxForward.bind(mptIssuanceID, account);
553
554 return schema_->selectAccountMPTokenIssuanceTx.bind(mptIssuanceID, account);
555 }();
557 statement,
558 kAccountMPTokenIssuanceTxCursorBindIndex,
559 kAccountMPTokenIssuanceTxLimitBindIndex,
560 limit,
561 forward,
562 cursorIn,
563 yield
564 );
565 }
566
569 xrpl::uint192 const& mptID,
570 std::uint32_t const limit,
571 std::optional<xrpl::AccountID> const& cursorIn,
572 std::uint32_t const ledgerSequence,
573 boost::asio::yield_context yield
574 ) const override
575 {
576 auto const holderEntries = executor_.read(
577 yield,
578 schema_->selectMPTHolders,
579 mptID,
580 cursorIn.value_or(xrpl::AccountID(0)),
581 Limit{limit}
582 );
583
584 auto const& holderResults = holderEntries.value();
585 if (not holderResults.hasRows()) {
586 LOG(log_.debug()) << "No rows returned";
587 return {};
588 }
589
590 std::vector<xrpl::uint256> mptKeys;
591 std::optional<xrpl::AccountID> cursor;
592 for (auto const [holder] : extract<xrpl::AccountID>(holderResults)) {
593 mptKeys.push_back(xrpl::keylet::mptoken(mptID, holder).key);
594 cursor = holder;
595 }
596
597 auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield);
598
599 auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob const& mpt) {
600 return mpt.empty();
601 });
602
603 mptObjects.erase(it, mptObjects.end());
604
605 ASSERT(mptKeys.size() <= limit, "Number of keys can't exceed the limit");
606 if (mptKeys.size() == limit)
607 return {mptObjects, cursor};
608
609 return {mptObjects, {}};
610 }
611
612 std::optional<Blob>
614 xrpl::uint256 const& key,
615 std::uint32_t const sequence,
616 boost::asio::yield_context yield
617 ) const override
618 {
619 LOG(log_.debug()) << "Fetching ledger object for seq " << sequence
620 << ", key = " << xrpl::to_string(key);
621 if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
622 if (auto const result = res->template get<Blob>(); result) {
623 if (result->size())
624 return result;
625 } else {
626 LOG(log_.debug()) << "Could not fetch ledger object - no rows";
627 }
628 } else {
629 LOG(log_.error()) << "Could not fetch ledger object: " << res.error();
630 }
631
632 return std::nullopt;
633 }
634
635 std::optional<std::uint32_t>
637 xrpl::uint256 const& key,
638 std::uint32_t const sequence,
639 boost::asio::yield_context yield
640 ) const override
641 {
642 LOG(log_.debug()) << "Fetching ledger object for seq " << sequence
643 << ", key = " << xrpl::to_string(key);
644 if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
645 if (auto const result = res->template get<Blob, std::uint32_t>(); result) {
646 auto [_, seq] = *result;
647 return seq;
648 }
649 LOG(log_.debug()) << "Could not fetch ledger object sequence - no rows";
650 } else {
651 LOG(log_.error()) << "Could not fetch ledger object sequence: " << res.error();
652 }
653
654 return std::nullopt;
655 }
656
657 std::optional<TransactionAndMetadata>
658 fetchTransaction(xrpl::uint256 const& hash, boost::asio::yield_context yield) const override
659 {
660 if (auto const res = executor_.read(yield, schema_->selectTransaction, hash); res) {
661 if (auto const maybeValue = res->template get<Blob, Blob, uint32_t, uint32_t>();
662 maybeValue) {
663 auto [transaction, meta, seq, date] = *maybeValue;
664 return std::make_optional<TransactionAndMetadata>(transaction, meta, seq, date);
665 }
666
667 LOG(log_.debug()) << "Could not fetch transaction - no rows";
668 } else {
669 LOG(log_.error()) << "Could not fetch transaction: " << res.error();
670 }
671
672 return std::nullopt;
673 }
674
675 std::optional<xrpl::uint256>
677 xrpl::uint256 key,
678 std::uint32_t const ledgerSequence,
679 boost::asio::yield_context yield
680 ) const override
681 {
682 if (auto const res = executor_.read(yield, schema_->selectSuccessor, key, ledgerSequence);
683 res) {
684 if (auto const result = res->template get<xrpl::uint256>(); result) {
685 if (*result == kLastKey)
686 return std::nullopt;
687 return result;
688 }
689
690 LOG(log_.debug()) << "Could not fetch successor - no rows";
691 } else {
692 LOG(log_.error()) << "Could not fetch successor: " << res.error();
693 }
694
695 return std::nullopt;
696 }
697
698 std::vector<TransactionAndMetadata>
700 std::vector<xrpl::uint256> const& hashes,
701 boost::asio::yield_context yield
702 ) const override
703 {
704 if (hashes.empty())
705 return {};
706
707 auto const numHashes = hashes.size();
708 std::vector<TransactionAndMetadata> results;
709 results.reserve(numHashes);
710
711 std::vector<Statement> statements;
712 statements.reserve(numHashes);
713
714 auto const timeDiff = util::timed([this, yield, &results, &hashes, &statements]() {
715 // TODO: seems like a job for "hash IN (list of hashes)" instead?
716 std::transform(
717 std::cbegin(hashes),
718 std::cend(hashes),
719 std::back_inserter(statements),
720 [this](auto const& hash) { return schema_->selectTransaction.bind(hash); }
721 );
722
723 auto const entries = executor_.readEach(yield, statements);
724 std::transform(
725 std::cbegin(entries),
726 std::cend(entries),
727 std::back_inserter(results),
728 [](auto const& res) -> TransactionAndMetadata {
729 if (auto const maybeRow = res.template get<Blob, Blob, uint32_t, uint32_t>();
730 maybeRow)
731 return *maybeRow;
732
733 return {};
734 }
735 );
736 });
737
738 ASSERT(numHashes == results.size(), "Number of hashes and results must match");
739 LOG(log_.debug()) << "Fetched " << numHashes << " transactions from database in "
740 << timeDiff << " milliseconds";
741 return results;
742 }
743
744 std::vector<Blob>
746 std::vector<xrpl::uint256> const& keys,
747 std::uint32_t const sequence,
748 boost::asio::yield_context yield
749 ) const override
750 {
751 if (keys.empty())
752 return {};
753
754 auto const numKeys = keys.size();
755 LOG(log_.trace()) << "Fetching " << numKeys << " objects";
756
757 std::vector<Blob> results;
758 results.reserve(numKeys);
759
760 std::vector<Statement> statements;
761 statements.reserve(numKeys);
762
763 // TODO: seems like a job for "key IN (list of keys)" instead?
764 std::transform(
765 std::cbegin(keys),
766 std::cend(keys),
767 std::back_inserter(statements),
768 [this, &sequence](auto const& key) { return schema_->selectObject.bind(key, sequence); }
769 );
770
771 auto const entries = executor_.readEach(yield, statements);
772 std::transform(
773 std::cbegin(entries),
774 std::cend(entries),
775 std::back_inserter(results),
776 [](auto const& res) -> Blob {
777 if (auto const maybeValue = res.template get<Blob>(); maybeValue)
778 return *maybeValue;
779
780 return {};
781 }
782 );
783
784 LOG(log_.trace()) << "Fetched " << numKeys << " objects";
785 return results;
786 }
787
788 std::vector<LedgerObject>
790 std::uint32_t const ledgerSequence,
791 boost::asio::yield_context yield
792 ) const override
793 {
794 auto const [keys, timeDiff] =
795 util::timed([this, &ledgerSequence, yield]() -> std::vector<xrpl::uint256> {
796 auto const res = executor_.read(yield, schema_->selectDiff, ledgerSequence);
797 if (not res) {
798 LOG(log_.error()) << "Could not fetch ledger diff: " << res.error()
799 << "; ledger = " << ledgerSequence;
800 return {};
801 }
802
803 auto const& results = res.value();
804 if (not results) {
805 LOG(log_.error())
806 << "Could not fetch ledger diff - no rows; ledger = " << ledgerSequence;
807 return {};
808 }
809
810 std::vector<xrpl::uint256> resultKeys;
811 for (auto [key] : extract<xrpl::uint256>(results))
812 resultKeys.push_back(key);
813
814 return resultKeys;
815 });
816
817 // one of the above errors must have happened
818 if (keys.empty())
819 return {};
820
821 LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from database in "
822 << timeDiff << " milliseconds";
823
824 auto const objs = fetchLedgerObjects(keys, ledgerSequence, yield);
825 std::vector<LedgerObject> results;
826 results.reserve(keys.size());
827
828 std::transform(
829 std::cbegin(keys),
830 std::cend(keys),
831 std::cbegin(objs),
832 std::back_inserter(results),
833 [](auto const& key, auto const& obj) { return LedgerObject{key, obj}; }
834 );
835
836 return results;
837 }
838
839 std::optional<std::string>
841 std::string const& migratorName,
842 boost::asio::yield_context yield
843 ) const override
844 {
845 auto const res = executor_.read(yield, schema_->selectMigratorStatus, Text(migratorName));
846 if (not res) {
847 LOG(log_.error()) << "Could not fetch migrator status: " << res.error();
848 return {};
849 }
850
851 auto const& results = res.value();
852 if (not results) {
853 return {};
854 }
855
856 for (auto [statusString] : extract<std::string>(results))
857 return statusString;
858
859 return {};
860 }
861
862 std::expected<std::vector<std::pair<boost::uuids::uuid, std::string>>, std::string>
863 fetchClioNodesData(boost::asio::yield_context yield) const override
864 {
865 auto const readResult = executor_.read(yield, schema_->selectClioNodesData);
866 if (not readResult)
867 return std::unexpected{readResult.error().message()};
868
869 std::vector<std::pair<boost::uuids::uuid, std::string>> result;
870
871 for (auto [uuid, message] : extract<boost::uuids::uuid, std::string>(*readResult)) {
872 result.emplace_back(uuid, std::move(message));
873 }
874
875 return result;
876 }
877
878 void
879 doWriteLedgerObject(std::string&& key, std::uint32_t const seq, std::string&& blob) override
880 {
881 LOG(log_.trace()) << " Writing ledger object " << key.size() << ":" << seq << " ["
882 << blob.size() << " bytes]";
883
884 if (range_)
885 executor_.write(schema_->insertDiff, seq, key);
886
887 executor_.write(schema_->insertObject, std::move(key), seq, std::move(blob));
888 }
889
890 void
891 writeSuccessor(std::string&& key, std::uint32_t const seq, std::string&& successor) override
892 {
893 LOG(log_.trace()) << "Writing successor. key = " << key.size() << " bytes. "
894 << " seq = " << std::to_string(seq) << " successor = " << successor.size()
895 << " bytes.";
896 ASSERT(!key.empty(), "Key must not be empty");
897 ASSERT(!successor.empty(), "Successor must not be empty");
898
899 executor_.write(schema_->insertSuccessor, std::move(key), seq, std::move(successor));
900 }
901
902 void
903 writeAccountTransactions(std::vector<AccountTransactionsData> data) override
904 {
905 std::vector<Statement> statements;
906 statements.reserve(data.size() * 10); // assume 10 transactions avg
907
908 for (auto& record : data) {
909 std::ranges::transform(
910 record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
911 return schema_->insertAccountTx.bind(
912 std::forward<decltype(account)>(account),
913 std::make_tuple(record.ledgerSequence, record.transactionIndex),
914 record.txHash
915 );
916 }
917 );
918 }
919
920 executor_.write(std::move(statements));
921 }
922
923 void
925 {
926 std::vector<Statement> statements;
927 statements.reserve(record.accounts.size());
928
929 std::ranges::transform(
930 record.accounts, std::back_inserter(statements), [this, &record](auto&& account) {
931 return schema_->insertAccountTx.bind(
932 std::forward<decltype(account)>(account),
933 std::make_tuple(record.ledgerSequence, record.transactionIndex),
934 record.txHash
935 );
936 }
937 );
938
939 executor_.write(std::move(statements));
940 }
941
942 void
943 writeNFTTransactions(std::vector<NFTTransactionsData> const& data) override
944 {
945 std::vector<Statement> statements;
946 statements.reserve(data.size());
947
948 std::ranges::transform(data, std::back_inserter(statements), [this](auto const& record) {
949 return schema_->insertNFTTx.bind(
950 record.tokenID,
951 std::make_tuple(record.ledgerSequence, record.transactionIndex),
952 record.txHash
953 );
954 });
955
956 executor_.write(std::move(statements));
957 }
958
959 void
961 std::vector<MPTokenIssuanceTransactionsData> const& data
962 ) override
963 {
964 std::vector<Statement> statements;
965 statements.reserve(data.size());
966
967 std::ranges::transform(data, std::back_inserter(statements), [this](auto const& record) {
968 return schema_->insertMPTokenIssuanceTx.bind(
969 record.mptIssuanceID,
970 std::make_tuple(record.ledgerSequence, record.transactionIndex),
971 record.txHash
972 );
973 });
974
975 executor_.write(std::move(statements));
976 }
977
978 void
980 std::vector<MPTokenIssuanceTransactionsData> const& data
981 ) override
982 {
983 std::size_t numStatements = 0u;
984 for (auto const& record : data)
985 numStatements += record.accounts.size();
986
987 std::vector<Statement> statements;
988 statements.reserve(numStatements);
989
990 for (auto const& record : data) {
991 std::ranges::transform(
992 record.accounts,
993 std::back_inserter(statements),
994 [this, &record](auto const& account) {
995 return schema_->insertAccountMPTokenIssuanceTx.bind(
996 record.mptIssuanceID,
997 account,
998 std::make_tuple(record.ledgerSequence, record.transactionIndex),
999 record.txHash
1000 );
1001 }
1002 );
1003 }
1004
1005 executor_.write(std::move(statements));
1006 }
1007
1008 void
1010 std::string&& hash,
1011 std::uint32_t const seq,
1012 std::uint32_t const date,
1013 std::string&& transaction,
1014 std::string&& metadata
1015 ) override
1016 {
1017 LOG(log_.trace()) << "Writing txn to database";
1018
1019 executor_.write(schema_->insertLedgerTransaction, seq, hash);
1020 executor_.write(
1021 schema_->insertTransaction,
1022 std::move(hash),
1023 seq,
1024 date,
1025 std::move(transaction),
1026 std::move(metadata)
1027 );
1028 }
1029
1030 void
1031 writeNFTs(std::vector<NFTsData> const& data) override
1032 {
1033 std::vector<Statement> statements;
1034 statements.reserve(data.size() * 3);
1035
1036 for (NFTsData const& record : data) {
1037 if (!record.onlyUriChanged) {
1038 statements.push_back(schema_->insertNFT.bind(
1039 record.tokenID, record.ledgerSequence, record.owner, record.isBurned
1040 ));
1041
1042 // If `uri` is set (and it can be set to an empty uri), we know this
1043 // is a net-new NFT. That is, this NFT has not been seen before by
1044 // us _OR_ it is in the extreme edge case of a re-minted NFT ID with
1045 // the same NFT ID as an already-burned token. In this case, we need
1046 // to record the URI and link to the issuer_nf_tokens table.
1047 if (record.uri) {
1048 statements.push_back(schema_->insertIssuerNFT.bind(
1049 xrpl::nft::getIssuer(record.tokenID),
1050 static_cast<uint32_t>(xrpl::nft::getTaxon(record.tokenID)),
1051 record.tokenID
1052 ));
1053 statements.push_back(schema_->insertNFTURI.bind(
1054 record.tokenID, record.ledgerSequence, *record.uri
1055 ));
1056 }
1057 } else {
1058 // only uri changed, we update the uri table only
1059 statements.push_back(
1060 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1061 schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, *record.uri)
1062 );
1063 }
1064 }
1065
1066 executor_.writeEach(std::move(statements));
1067 }
1068
1069 void
1070 writeMPTHolders(std::vector<MPTHolderData> const& data) override
1071 {
1072 std::vector<Statement> statements;
1073 statements.reserve(data.size());
1074 for (auto [mptId, holder] : data)
1075 statements.push_back(schema_->insertMPTHolder.bind(mptId, holder));
1076
1077 executor_.write(std::move(statements));
1078 }
1079
1080 void
1081 startWrites() const override
1082 {
1083 // Note: no-op in original implementation too.
1084 // probably was used in PG to start a transaction or smth.
1085 }
1086
1087 void
1088 writeMigratorStatus(std::string const& migratorName, std::string const& status) override
1089 {
1090 executor_.writeSync(
1091 schema_->insertMigratorStatus,
1092 data::cassandra::Text{migratorName},
1093 data::cassandra::Text(status)
1094 );
1095 }
1096
1097 void
1098 writeNodeMessage(boost::uuids::uuid const& uuid, std::string message) override
1099 {
1100 executor_.writeSync(
1101 schema_->updateClioNodeMessage, data::cassandra::Text{std::move(message)}, uuid
1102 );
1103 }
1104
1105 bool
1106 isTooBusy() const override
1107 {
1108 return executor_.isTooBusy();
1109 }
1110
1111 boost::json::object
1112 stats() const override
1113 {
1114 return executor_.stats();
1115 }
1116
1117protected:
1124 bool
1125 executeSyncUpdate(Statement statement)
1126 {
1127 auto const res = executor_.writeSync(statement);
1128 auto maybeSuccess = res->template get<bool>();
1129 if (not maybeSuccess) {
1130 LOG(log_.error()) << "executeSyncUpdate - error getting result - no row";
1131 return false;
1132 }
1133
1134 if (not *maybeSuccess) {
1135 LOG(log_.warn()) << "Update failed. Checking if DB state is what we expect";
1136
1137 // error may indicate that another writer wrote something.
1138 // in this case let's just compare the current state of things
1139 // against what we were trying to write in the first place and
1140 // use that as the source of truth for the result.
1141 auto rng = hardFetchLedgerRangeNoThrow();
1142 return rng && rng->maxSequence == ledgerSequence_;
1143 }
1144
1145 return true;
1146 }
1147
1166 Statement const& statement,
1167 std::size_t const cursorIdx,
1168 std::size_t const limitIdx,
1169 std::uint32_t const limit,
1170 bool const forward,
1171 std::optional<TransactionsCursor> const& cursorIn,
1172 boost::asio::yield_context yield
1173 ) const
1174 {
1175 auto rng = fetchLedgerRange();
1176 if (!rng)
1177 return {.txns = {}, .cursor = {}};
1178
1179 auto cursor = cursorIn;
1180 if (cursor.has_value()) {
1181 statement.bindAt(cursorIdx, cursor->asTuple());
1182 } else {
1183 // Forward uses the nft_history-style inclusive lower bound; reverse starts just past
1184 // the latest validated ledger so its exclusive `<` query includes that ledger's rows.
1185 auto const ledgerSequence = forward ? rng->minSequence : rng->maxSequence;
1186 auto const transactionIndex = forward ? 0u : std::numeric_limits<std::uint32_t>::max();
1187 statement.bindAt(cursorIdx, std::make_tuple(ledgerSequence, transactionIndex));
1188 }
1189
1190 statement.bindAt(limitIdx, Limit{limit});
1191
1192 auto const res = executor_.read(yield, statement);
1193 auto const& results = res.value();
1194 if (not results.hasRows()) {
1195 LOG(log_.debug()) << "No rows returned";
1196 return {};
1197 }
1198
1199 std::vector<xrpl::uint256> hashes = {};
1200 auto numRows = results.numRows();
1201
1202 for (auto const& [hash, data] :
1203 extract<xrpl::uint256, std::tuple<uint32_t, uint32_t>>(results)) {
1204 hashes.push_back(hash);
1205
1206 if (--numRows == 0) {
1207 LOG(log_.debug()) << "Setting cursor";
1208 cursor = data;
1209
1210 // forward queries by ledger/tx sequence `>=`
1211 // so we have to advance the index by one
1212 if (forward)
1213 ++cursor->transactionIndex;
1214 }
1215 }
1216
1217 auto txns = fetchTransactions(hashes, yield);
1218 LOG(log_.debug()) << "MPTokenIssuance Txns = " << txns.size();
1219
1220 if (txns.size() == limit) {
1221 LOG(log_.debug()) << "Returning cursor";
1222 return {std::move(txns), cursor};
1223 }
1224
1225 return {std::move(txns), {}};
1226 }
1227};
1228
1229} // namespace data::cassandra
BackendInterface(LedgerCacheInterface &cache)
Construct a new backend interface instance.
Definition BackendInterface.hpp:225
std::optional< LedgerRange > hardFetchLedgerRangeNoThrow() const
Fetches the ledger range from DB retrying until no DatabaseError 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:256
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:1088
std::optional< LedgerRange > hardFetchLedgerRange(boost::asio::yield_context yield) const override
Fetches the ledger range from DB.
Definition CassandraBackendFamily.hpp:318
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:267
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:541
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:403
void startWrites() const override
Starts a write transaction with the DB. No-op for cassandra.
Definition CassandraBackendFamily.hpp:1081
void doWriteLedgerObject(std::string &&key, std::uint32_t const seq, std::string &&blob) override
Writes a ledger object to the database.
Definition CassandraBackendFamily.hpp:879
std::chrono::milliseconds initialRetryDelay() const override
Definition CassandraBackendFamily.hpp:146
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:636
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:298
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:161
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:613
void writeNFTs(std::vector< NFTsData > const &data) override
Writes NFTs to the database.
Definition CassandraBackendFamily.hpp:1031
void writeNFTTransactions(std::vector< NFTTransactionsData > const &data) override
Write NFTs transactions.
Definition CassandraBackendFamily.hpp:943
void writeNodeMessage(boost::uuids::uuid const &uuid, std::string message) override
Write a node message. Used by ClusterCommunicationService.
Definition CassandraBackendFamily.hpp:1098
std::optional< std::uint32_t > fetchLatestLedgerSequence(boost::asio::yield_context yield) const override
Fetches the latest ledger sequence.
Definition CassandraBackendFamily.hpp:247
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:979
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:568
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:676
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:442
bool isTooBusy() const override
Definition CassandraBackendFamily.hpp:1106
void writeMPTHolders(std::vector< MPTHolderData > const &data) override
Write accounts that started holding onto a MPT.
Definition CassandraBackendFamily.hpp:1070
void writeAccountTransaction(AccountTransactionsData record) override
Write a new account transaction.
Definition CassandraBackendFamily.hpp:924
void writeSuccessor(std::string &&key, std::uint32_t const seq, std::string &&successor) override
Write a new successor.
Definition CassandraBackendFamily.hpp:891
void waitForWritesToFinish() override
Wait for all pending writes to finish.
Definition CassandraBackendFamily.hpp:231
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:515
void writeLedger(xrpl::LedgerHeader const &ledgerHeader, std::string &&blob) override
Writes to a specific ledger.
Definition CassandraBackendFamily.hpp:237
std::optional< TransactionAndMetadata > fetchTransaction(xrpl::uint256 const &hash, boost::asio::yield_context yield) const override
Fetches a specific transaction.
Definition CassandraBackendFamily.hpp:658
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:1165
boost::json::object stats() const override
Definition CassandraBackendFamily.hpp:1112
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:840
std::chrono::milliseconds maxRetryDelay() const override
Definition CassandraBackendFamily.hpp:155
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:863
void writeAccountTransactions(std::vector< AccountTransactionsData > data) override
Write a new set of account transactions.
Definition CassandraBackendFamily.hpp:903
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:745
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:1125
void writeMPTokenIssuanceTransactions(std::vector< MPTokenIssuanceTransactionsData > const &data) override
Write MPTokenIssuance transaction index rows to the mptoken_issuance_transactions table.
Definition CassandraBackendFamily.hpp:960
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:1009
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:366
std::vector< TransactionAndMetadata > fetchTransactions(std::vector< xrpl::uint256 > const &hashes, boost::asio::yield_context yield) const override
Fetches multiple transactions.
Definition CassandraBackendFamily.hpp:699
std::vector< LedgerObject > fetchLedgerDiff(std::uint32_t const ledgerSequence, boost::asio::yield_context yield) const override
Returns the difference between ledgers.
Definition CassandraBackendFamily.hpp:789
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:356
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