Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
Tx.hpp
1#pragma once
2
3#include "data/BackendInterface.hpp"
4#include "data/Types.hpp"
5#include "etl/ETLServiceInterface.hpp"
6#include "rpc/Errors.hpp"
7#include "rpc/JS.hpp"
8#include "rpc/RPCHelpers.hpp"
9#include "rpc/common/JsonBool.hpp"
10#include "rpc/common/Specs.hpp"
11#include "rpc/common/Types.hpp"
12#include "rpc/common/Validators.hpp"
13#include "util/Assert.hpp"
14#include "util/JsonUtils.hpp"
15
16#include <boost/asio/spawn.hpp>
17#include <boost/json/conversion.hpp>
18#include <boost/json/object.hpp>
19#include <boost/json/value.hpp>
20#include <boost/json/value_to.hpp>
21#include <fmt/format.h>
22#include <xrpl/basics/base_uint.h>
23#include <xrpl/basics/chrono.h>
24#include <xrpl/basics/strHex.h>
25#include <xrpl/protocol/ErrorCodes.h>
26#include <xrpl/protocol/LedgerHeader.h>
27#include <xrpl/protocol/jss.h>
28
29#include <cstdint>
30#include <memory>
31#include <optional>
32#include <string>
33#include <utility>
34
35namespace rpc {
36
42class TxHandler {
43 std::shared_ptr<BackendInterface> sharedPtrBackend_;
44 std::shared_ptr<etl::ETLServiceInterface const> etl_;
45
46public:
50 struct Output {
51 uint32_t date = 0u;
52 std::string hash = {}; // NOLINT(readability-redundant-member-init)
53 uint32_t ledgerIndex = 0u;
54 std::optional<boost::json::object> meta =
55 std::nullopt; // NOLINT(readability-redundant-member-init)
56 std::optional<boost::json::object> tx =
57 std::nullopt; // NOLINT(readability-redundant-member-init)
58 std::optional<std::string> metaStr =
59 std::nullopt; // NOLINT(readability-redundant-member-init)
60 std::optional<std::string> txStr =
61 std::nullopt; // NOLINT(readability-redundant-member-init)
62 std::optional<std::string> ctid =
63 std::nullopt; // NOLINT(readability-redundant-member-init) ctid when binary=true
64 std::optional<ripple::LedgerHeader> ledgerHeader =
65 std::nullopt; // NOLINT(readability-redundant-member-init) ledger hash when apiVersion
66 // >= 2
67 uint32_t apiVersion = 0u;
68 bool validated = true;
69 };
70
74 struct Input {
75 std::optional<std::string> transaction;
76 std::optional<std::string> ctid;
77 bool binary = false;
78 std::optional<uint32_t> minLedger;
79 std::optional<uint32_t> maxLedger;
80 };
81
82 using Result = HandlerReturnType<Output>;
83
91 std::shared_ptr<BackendInterface> sharedPtrBackend,
92 std::shared_ptr<etl::ETLServiceInterface const> const& etl
93 )
94 : sharedPtrBackend_(std::move(sharedPtrBackend)), etl_(etl)
95 {
96 }
97
104 static RpcSpecConstRef
105 spec(uint32_t apiVersion)
106 {
107 static RpcSpec const kRpcSpecForV1 = {
109 {JS(min_ledger), validation::Type<uint32_t>{}},
110 {JS(max_ledger), validation::Type<uint32_t>{}},
111 {JS(ctid), validation::Type<std::string>{}},
112 };
113
114 static auto const kRpcSpec =
115 RpcSpec{kRpcSpecForV1, {{JS(binary), validation::Type<bool>{}}}};
116
117 return apiVersion == 1 ? kRpcSpecForV1 : kRpcSpec;
118 }
119
127 [[nodiscard]] Result
128 process(Input const& input, Context const& ctx) const
129 {
130 if (input.ctid && input.transaction) // ambiguous identifier
131 return Error{Status{RippledError::rpcINVALID_PARAMS}};
132
133 if (!input.ctid && !input.transaction) // at least one identifier must be supplied
134 return Error{Status{RippledError::rpcINVALID_PARAMS}};
135
136 static constexpr auto kMaxLedgerRange = 1000u;
137 auto const rangeSupplied = input.minLedger && input.maxLedger;
138
139 if (rangeSupplied) {
140 if (*input.minLedger > *input.maxLedger)
141 return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
142
143 if (*input.maxLedger - *input.minLedger > kMaxLedgerRange)
144 return Error{Status{RippledError::rpcEXCESSIVE_LGR_RANGE}};
145 }
146
147 std::optional<uint32_t> currentNetId = std::nullopt;
148 if (auto const& etlState = etl_->getETLState(); etlState.has_value())
149 currentNetId = etlState->networkID;
150
151 std::optional<data::TransactionAndMetadata> dbResponse;
152
153 if (input.ctid) {
154 auto const ctid = rpc::decodeCTID(*input.ctid);
155 if (!ctid)
156 return Error{Status{RippledError::rpcINVALID_PARAMS}};
157
158 auto const [lgrSeq, txnIdx, netId] = *ctid;
159 // when current network id is available, let us check the network id from parameter
160 if (currentNetId && netId != *currentNetId) {
161 return Error{Status{
162 RippledError::rpcWRONG_NETWORK,
163 fmt::format(
164 "Wrong network. You should submit this request to a node running on "
165 "NetworkID: {}",
166 netId
167 )
168 }};
169 }
170
171 dbResponse = fetchTxViaCtid(lgrSeq, txnIdx, ctx.yield);
172 } else {
173 dbResponse = sharedPtrBackend_->fetchTransaction(
174 ripple::uint256{input.transaction->c_str()}, ctx.yield
175 );
176 }
177
178 auto output = TxHandler::Output{.apiVersion = ctx.apiVersion};
179
180 if (!dbResponse) {
181 if (rangeSupplied && input.transaction) // ranges not for ctid
182 {
183 auto const range = sharedPtrBackend_->fetchLedgerRange();
184 ASSERT(range.has_value(), "Tx's ledger range must be available");
185
186 // NOLINTBEGIN(bugprone-unchecked-optional-access)
187 auto const searchedAll = range->maxSequence >= *input.maxLedger &&
188 range->minSequence <= *input.minLedger;
189 // NOLINTEND(bugprone-unchecked-optional-access)
190
191 boost::json::object extra;
192 extra["searched_all"] = searchedAll;
193
194 return Error{Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)}};
195 }
196
197 return Error{Status{RippledError::rpcTXN_NOT_FOUND}};
198 }
199
200 auto const [txn, meta] =
201 toExpandedJson(*dbResponse, ctx.apiVersion, NFTokenjson::ENABLE, currentNetId);
202
203 if (!input.binary) {
204 output.tx = txn;
205 output.meta = meta;
206 } else {
207 output.txStr = ripple::strHex(dbResponse->transaction);
208 output.metaStr = ripple::strHex(dbResponse->metadata);
209
210 // input.transaction might be not available, get hash via tx object
211 if (txn.contains(JS(hash)))
212 output.hash = txn.at(JS(hash)).as_string();
213 }
214
215 // append ctid here to mimic rippled behavior
216 auto const txnIdx = boost::json::value_to<uint64_t>(meta.at("TransactionIndex"));
217 if (txnIdx <= 0xFFFFU && dbResponse->ledgerSequence < 0x0FFF'FFFFUL && currentNetId &&
218 *currentNetId <= 0xFFFFU) {
219 output.ctid = rpc::encodeCTID(
220 dbResponse->ledgerSequence,
221 static_cast<uint16_t>(txnIdx),
222 static_cast<uint16_t>(*currentNetId)
223 );
224 }
225
226 output.date = dbResponse->date;
227 output.ledgerIndex = dbResponse->ledgerSequence;
228
229 // fetch ledger hash
230 if (ctx.apiVersion > 1u) {
231 output.ledgerHeader =
232 sharedPtrBackend_->fetchLedgerBySequence(dbResponse->ledgerSequence, ctx.yield);
233 }
234
235 return output;
236 }
237
238private:
239 [[nodiscard]] std::optional<data::TransactionAndMetadata>
240 fetchTxViaCtid(uint32_t ledgerSeq, uint32_t txId, boost::asio::yield_context yield) const
241 {
242 auto const txs = sharedPtrBackend_->fetchAllTransactionsInLedger(ledgerSeq, yield);
243
244 for (auto const& tx : txs) {
245 auto const [txn, meta] = deserializeTxPlusMeta(tx, ledgerSeq);
246
247 if (meta->getIndex() == txId)
248 return tx;
249 }
250
251 return std::nullopt;
252 }
253
254 friend void
255 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
256 {
257 auto const getJsonV1 = [&]() {
258 auto obj = boost::json::object{};
259
260 if (output.tx) {
261 obj = *output.tx;
262 obj[JS(meta)] = *output.meta;
263 } else {
264 obj[JS(meta)] = *output.metaStr;
265 obj[JS(tx)] = *output.txStr;
266 obj[JS(hash)] = output.hash;
267 }
268
269 obj[JS(validated)] = output.validated;
270 obj[JS(date)] = output.date;
271 obj[JS(ledger_index)] = output.ledgerIndex;
272 obj[JS(inLedger)] = output.ledgerIndex;
273 return obj;
274 };
275
276 auto const getJsonV2 = [&]() {
277 auto obj = boost::json::object{};
278
279 if (output.tx) {
280 obj[JS(tx_json)] = *output.tx;
281 obj[JS(tx_json)].as_object()[JS(date)] = output.date;
282 if (output.ctid)
283 obj[JS(tx_json)].as_object()[JS(ctid)] = *output.ctid;
284
285 obj[JS(tx_json)].as_object()[JS(ledger_index)] = output.ledgerIndex;
286 // move hash from tx_json to root
287 if (obj[JS(tx_json)].as_object().contains(JS(hash))) {
288 obj[JS(hash)] = obj[JS(tx_json)].as_object()[JS(hash)];
289 obj[JS(tx_json)].as_object().erase(JS(hash));
290 }
291 obj[JS(meta)] = *output.meta;
292 } else {
293 obj[JS(meta_blob)] = *output.metaStr;
294 obj[JS(tx_blob)] = *output.txStr;
295 obj[JS(hash)] = output.hash;
296 }
297
298 obj[JS(validated)] = output.validated;
299 obj[JS(ledger_index)] = output.ledgerIndex;
300
301 if (output.ledgerHeader) {
302 obj[JS(ledger_hash)] = ripple::strHex(output.ledgerHeader->hash);
303 obj[JS(close_time_iso)] = ripple::to_string_iso(output.ledgerHeader->closeTime);
304 }
305 return obj;
306 };
307
308 auto obj = output.apiVersion > 1u ? getJsonV2() : getJsonV1();
309
310 if (output.ctid)
311 obj[JS(ctid)] = *output.ctid;
312
313 jv = std::move(obj);
314 }
315
316 friend Input
317 tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
318 {
319 auto input = TxHandler::Input{};
320 auto const& jsonObject = jv.as_object();
321
322 if (jsonObject.contains(JS(transaction)))
323 input.transaction = boost::json::value_to<std::string>(jv.at(JS(transaction)));
324
325 if (jsonObject.contains(JS(ctid))) {
326 input.ctid = boost::json::value_to<std::string>(jv.at(JS(ctid)));
327 input.ctid = util::toUpper(*input.ctid);
328 }
329
330 if (jsonObject.contains(JS(binary)))
331 input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
332
333 if (jsonObject.contains(JS(min_ledger)))
334 input.minLedger = util::integralValueAs<uint32_t>(jv.at(JS(min_ledger)));
335
336 if (jsonObject.contains(JS(max_ledger)))
337 input.maxLedger = util::integralValueAs<uint32_t>(jv.at(JS(max_ledger)));
338
339 return input;
340 }
341};
342
343} // namespace rpc
TxHandler(std::shared_ptr< BackendInterface > sharedPtrBackend, std::shared_ptr< etl::ETLServiceInterface const > const &etl)
Construct a new TxHandler object.
Definition Tx.hpp:90
Result process(Input const &input, Context const &ctx) const
Process the Tx command.
Definition Tx.hpp:128
static RpcSpecConstRef spec(uint32_t apiVersion)
Returns the API specification for the command.
Definition Tx.hpp:105
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:18
std::pair< std::shared_ptr< ripple::STTx const >, std::shared_ptr< ripple::STObject const > > deserializeTxPlusMeta(data::TransactionAndMetadata const &blobs)
Deserialize a TransactionAndMetadata into a pair of STTx and STObject.
Definition RPCHelpers.cpp:184
RpcSpec const & RpcSpecConstRef
An alias for a const reference to RpcSpec.
Definition Specs.hpp:130
std::expected< OutputType, Status > HandlerReturnType
Return type for each individual handler.
Definition Types.hpp:62
std::optional< std::string > encodeCTID(uint32_t ledgerSeq, uint16_t txnIndex, uint16_t networkId) noexcept
Encode CTID as string.
Definition RPCHelpers.cpp:270
std::optional< std::tuple< uint32_t, uint16_t, uint16_t > > decodeCTID(T const ctid) noexcept
Decode the CTID from a string or a uint64_t.
Definition RPCHelpers.hpp:711
std::pair< boost::json::object, boost::json::object > toExpandedJson(data::TransactionAndMetadata const &blobs, std::uint32_t const apiVersion, NFTokenjson nftEnabled, std::optional< uint16_t > networkId)
Convert a TransactionAndMetadata to two JSON objects.
Definition RPCHelpers.cpp:234
std::unexpected< Status > Error
The type that represents just the error part of MaybeError.
Definition Types.hpp:56
Type integralValueAs(boost::json::value const &value)
Detects the type of number stored in value and casts it back to the requested Type.
Definition JsonUtils.hpp:110
std::string toUpper(std::string str)
Convert a string to uppercase.
Definition JsonUtils.hpp:42
Context of an RPC call.
Definition Types.hpp:99
Result type used to return responses or error statuses to the Webserver subsystem.
Definition Types.hpp:110
Represents a Specification of an entire RPC command.
Definition Specs.hpp:82
A status returned from any RPC handler.
Definition Errors.hpp:65
A struct to hold the input data for the command.
Definition Tx.hpp:74
A struct to hold the output data of the command.
Definition Tx.hpp:50
static CustomValidator uint256HexStringValidator
Provides a commonly used validator for uint256 hex string.
Definition Validators.hpp:551
Validates that the type of the value is one of the given types.
Definition Validators.hpp:128