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