Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
CassandraBackend.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2023, the clio developers.
5
6 Permission to use, copy, modify, and distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#pragma once
21
22#include "data/LedgerHeaderCache.hpp"
23#include "data/Types.hpp"
24#include "data/cassandra/CassandraBackendFamily.hpp"
25#include "data/cassandra/CassandraSchema.hpp"
26#include "data/cassandra/Concepts.hpp"
27#include "data/cassandra/Handle.hpp"
28#include "data/cassandra/SettingsProvider.hpp"
29#include "data/cassandra/Types.hpp"
30#include "data/cassandra/impl/ExecutionStrategy.hpp"
31#include "util/log/Logger.hpp"
32
33#include <boost/asio/spawn.hpp>
34#include <boost/json/object.hpp>
35#include <boost/uuid/string_generator.hpp>
36#include <boost/uuid/uuid.hpp>
37#include <cassandra.h>
38#include <fmt/format.h>
39#include <xrpl/basics/Blob.h>
40#include <xrpl/basics/base_uint.h>
41#include <xrpl/basics/strHex.h>
42#include <xrpl/protocol/AccountID.h>
43#include <xrpl/protocol/Indexes.h>
44#include <xrpl/protocol/LedgerHeader.h>
45#include <xrpl/protocol/nft.h>
46
47#include <algorithm>
48#include <cstddef>
49#include <cstdint>
50#include <iterator>
51#include <optional>
52#include <string>
53#include <tuple>
54#include <vector>
55
56namespace data::cassandra {
57
65template <
66 SomeSettingsProvider SettingsProviderType,
67 SomeExecutionStrategy ExecutionStrategyType,
68 typename FetchLedgerCacheType = FetchLedgerCache>
69class BasicCassandraBackend : public CassandraBackendFamily<
70 SettingsProviderType,
71 ExecutionStrategyType,
72 CassandraSchema<SettingsProviderType>,
73 FetchLedgerCacheType> {
74 using DefaultCassandraFamily = CassandraBackendFamily<
75 SettingsProviderType,
76 ExecutionStrategyType,
78 FetchLedgerCacheType>;
79
80 // protected because CassandraMigrationBackend inherits from this class
81protected:
82 using DefaultCassandraFamily::executor_;
83 using DefaultCassandraFamily::ledgerSequence_;
84 using DefaultCassandraFamily::log_;
85 using DefaultCassandraFamily::range_;
86 using DefaultCassandraFamily::schema_;
87
88public:
92 using DefaultCassandraFamily::DefaultCassandraFamily;
93
94 /*
95 * @brief Move constructor is deleted because handle_ is shared by reference with executor
96 */
97 BasicCassandraBackend(BasicCassandraBackend&&) = delete;
98
99 bool
100 doFinishWrites() override
101 {
102 this->waitForWritesToFinish();
103
104 if (!range_) {
105 executor_.writeSync(
106 schema_->updateLedgerRange, ledgerSequence_, false, ledgerSequence_
107 );
108 }
109
110 if (not this->executeSyncUpdate(
111 schema_->updateLedgerRange.bind(ledgerSequence_, true, ledgerSequence_ - 1)
112 )) {
113 LOG(log_.warn()) << "Update failed for ledger " << ledgerSequence_;
114 return false;
115 }
116
117 LOG(log_.info()) << "Committed ledger " << ledgerSequence_;
118 return true;
119 }
120
123 ripple::AccountID const& issuer,
124 std::optional<std::uint32_t> const& taxon,
125 std::uint32_t const ledgerSequence,
126 std::uint32_t const limit,
127 std::optional<ripple::uint256> const& cursorIn,
128 boost::asio::yield_context yield
129 ) const override
130 {
131 NFTsAndCursor ret;
132
133 Statement const idQueryStatement = [&taxon, &issuer, &cursorIn, &limit, this]() {
134 if (taxon.has_value()) {
135 auto r = schema_->selectNFTIDsByIssuerTaxon.bind(issuer);
136 r.bindAt(1, *taxon);
137 r.bindAt(2, cursorIn.value_or(ripple::uint256(0)));
138 r.bindAt(3, Limit{limit});
139 return r;
140 }
141
142 auto r = schema_->selectNFTIDsByIssuer.bind(issuer);
143 r.bindAt(
144 1,
145 std::make_tuple(
146 cursorIn.has_value() ? ripple::nft::toUInt32(ripple::nft::getTaxon(*cursorIn))
147 : 0,
148 cursorIn.value_or(ripple::uint256(0))
149 )
150 );
151 r.bindAt(2, Limit{limit});
152 return r;
153 }();
154
155 // Query for all the NFTs issued by the account, potentially filtered by the taxon
156 auto const res = executor_.read(yield, idQueryStatement);
157
158 auto const& idQueryResults = res.value();
159 if (not idQueryResults.hasRows()) {
160 LOG(log_.debug()) << "No rows returned";
161 return {};
162 }
163
164 std::vector<ripple::uint256> nftIDs;
165 for (auto const [nftID] : extract<ripple::uint256>(idQueryResults))
166 nftIDs.push_back(nftID);
167
168 if (nftIDs.empty())
169 return ret;
170
171 if (nftIDs.size() == limit)
172 ret.cursor = nftIDs.back();
173
174 std::vector<Statement> selectNFTStatements;
175 selectNFTStatements.reserve(nftIDs.size());
176
177 std::transform(
178 std::cbegin(nftIDs),
179 std::cend(nftIDs),
180 std::back_inserter(selectNFTStatements),
181 [&](auto const& nftID) { return schema_->selectNFT.bind(nftID, ledgerSequence); }
182 );
183
184 auto const nftInfos = executor_.readEach(yield, selectNFTStatements);
185
186 std::vector<Statement> selectNFTURIStatements;
187 selectNFTURIStatements.reserve(nftIDs.size());
188
189 std::transform(
190 std::cbegin(nftIDs),
191 std::cend(nftIDs),
192 std::back_inserter(selectNFTURIStatements),
193 [&](auto const& nftID) { return schema_->selectNFTURI.bind(nftID, ledgerSequence); }
194 );
195
196 auto const nftUris = executor_.readEach(yield, selectNFTURIStatements);
197
198 for (auto i = 0u; i < nftIDs.size(); i++) {
199 if (auto const maybeRow = nftInfos[i].template get<uint32_t, ripple::AccountID, bool>();
200 maybeRow.has_value()) {
201 auto [seq, owner, isBurned] = *maybeRow;
202 NFT nft(nftIDs[i], seq, owner, isBurned);
203 if (auto const maybeUri = nftUris[i].template get<ripple::Blob>();
204 maybeUri.has_value())
205 nft.uri = *maybeUri;
206 ret.nfts.push_back(nft);
207 }
208 }
209 return ret;
210 }
211
212 std::vector<ripple::uint256>
214 std::uint32_t number,
215 std::uint32_t pageSize,
216 std::uint32_t seq,
217 boost::asio::yield_context yield
218 ) const override
219 {
220 std::vector<ripple::uint256> liveAccounts;
221 std::optional<ripple::AccountID> lastItem;
222
223 while (liveAccounts.size() < number) {
224 Statement const statement = lastItem
225 ? schema_->selectAccountFromToken.bind(*lastItem, Limit{pageSize})
226 : schema_->selectAccountFromBeginning.bind(Limit{pageSize});
227
228 auto const res = executor_.read(yield, statement);
229 if (res) {
230 auto const& results = res.value();
231 if (not results.hasRows()) {
232 LOG(log_.debug()) << "No rows returned";
233 break;
234 }
235 // The results should not contain duplicates, we just filter out deleted accounts
236 std::vector<ripple::uint256> fullAccounts;
237 for (auto [account] : extract<ripple::AccountID>(results)) {
238 fullAccounts.push_back(ripple::keylet::account(account).key);
239 lastItem = account;
240 }
241 auto const objs = this->doFetchLedgerObjects(fullAccounts, seq, yield);
242
243 for (auto i = 0u; i < fullAccounts.size(); i++) {
244 if (not objs[i].empty()) {
245 if (liveAccounts.size() < number) {
246 liveAccounts.push_back(fullAccounts[i]);
247 } else {
248 break;
249 }
250 }
251 }
252 } else {
253 LOG(log_.error()) << "Could not fetch account from account_tx: " << res.error();
254 break;
255 }
256 }
257
258 return liveAccounts;
259 }
260};
261
262using CassandraBackend = BasicCassandraBackend<SettingsProvider, impl::DefaultExecutionStrategy<>>;
263
264} // namespace data::cassandra
std::vector< ripple::uint256 > fetchAccountRoots(std::uint32_t number, std::uint32_t pageSize, std::uint32_t seq, boost::asio::yield_context yield) const override
Fetch the specified number of account root object indexes by page, the accounts need to exist for seq...
Definition CassandraBackend.hpp:213
NFTsAndCursor fetchNFTsByIssuer(ripple::AccountID const &issuer, std::optional< std::uint32_t > const &taxon, std::uint32_t const ledgerSequence, std::uint32_t const limit, std::optional< ripple::uint256 > const &cursorIn, boost::asio::yield_context yield) const override
Fetches all NFTs issued by a given address.
Definition CassandraBackend.hpp:122
bool doFinishWrites() override
The implementation should wait for all pending writes to finish.
Definition CassandraBackend.hpp:100
CassandraBackendFamily(SettingsProviderType settingsProvider, data::LedgerCacheInterface &cache, bool readOnly)
Definition CassandraBackendFamily.hpp:107
std::vector< Blob > doFetchLedgerObjects(std::vector< ripple::uint256 > const &keys, std::uint32_t const sequence, boost::asio::yield_context yield) const override
Definition CassandraBackendFamily.hpp:685
Manages the DB schema and provides access to prepared statements.
Definition CassandraSchema.hpp:41
The requirements of an execution strategy.
Definition Concepts.hpp:54
The requirements of a settings provider.
Definition Concepts.hpp:43
This namespace implements a wrapper for the Cassandra C++ driver.
Definition CassandraBackendFamily.hpp:66
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:333
Represents a NFToken.
Definition Types.hpp:180
Represents a bundle of NFTs with a cursor to the next page.
Definition Types.hpp:246
A strong type wrapper for int32_t.
Definition Types.hpp:57