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 "etlng/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/core.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<etlng::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 = std::nullopt; // NOLINT(readability-redundant-member-init)
74 std::optional<boost::json::object> tx = std::nullopt; // NOLINT(readability-redundant-member-init)
75 std::optional<std::string> metaStr = std::nullopt; // NOLINT(readability-redundant-member-init)
76 std::optional<std::string> txStr = std::nullopt; // NOLINT(readability-redundant-member-init)
77 std::optional<std::string> ctid =
78 std::nullopt; // NOLINT(readability-redundant-member-init) ctid when binary=true
79 std::optional<ripple::LedgerHeader> ledgerHeader =
80 std::nullopt; // NOLINT(readability-redundant-member-init) ledger hash when apiVersion >= 2
81 uint32_t apiVersion = 0u;
82 bool validated = true;
83 };
84
88 struct Input {
89 std::optional<std::string> transaction;
90 std::optional<std::string> ctid;
91 bool binary = false;
92 std::optional<uint32_t> minLedger;
93 std::optional<uint32_t> maxLedger;
94 };
95
96 using Result = HandlerReturnType<Output>;
97
105 std::shared_ptr<BackendInterface> const& sharedPtrBackend,
106 std::shared_ptr<etlng::ETLServiceInterface const> const& etl
107 )
108 : sharedPtrBackend_(sharedPtrBackend), etl_(etl)
109 {
110 }
111
118 static RpcSpecConstRef
119 spec(uint32_t apiVersion)
120 {
121 static RpcSpec const kRPC_SPEC_FOR_V1 = {
123 {JS(min_ledger), validation::Type<uint32_t>{}},
124 {JS(max_ledger), validation::Type<uint32_t>{}},
125 {JS(ctid), validation::Type<std::string>{}},
126 };
127
128 static auto const kRPC_SPEC = RpcSpec{kRPC_SPEC_FOR_V1, {{JS(binary), validation::Type<bool>{}}}};
129
130 return apiVersion == 1 ? kRPC_SPEC_FOR_V1 : kRPC_SPEC;
131 }
132
140 Result
141 process(Input input, Context const& ctx) const
142 {
143 if (input.ctid && input.transaction) // ambiguous identifier
144 return Error{Status{RippledError::rpcINVALID_PARAMS}};
145
146 if (!input.ctid && !input.transaction) // at least one identifier must be supplied
147 return Error{Status{RippledError::rpcINVALID_PARAMS}};
148
149 static constexpr auto kMAX_LEDGER_RANGE = 1000u;
150 auto const rangeSupplied = input.minLedger && input.maxLedger;
151
152 if (rangeSupplied) {
153 if (*input.minLedger > *input.maxLedger)
154 return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
155
156 if (*input.maxLedger - *input.minLedger > kMAX_LEDGER_RANGE)
157 return Error{Status{RippledError::rpcEXCESSIVE_LGR_RANGE}};
158 }
159
160 std::optional<uint32_t> currentNetId = std::nullopt;
161 if (auto const& etlState = etl_->getETLState(); etlState.has_value())
162 currentNetId = etlState->networkID;
163
164 std::optional<data::TransactionAndMetadata> dbResponse;
165
166 if (input.ctid) {
167 auto const ctid = rpc::decodeCTID(*input.ctid);
168 if (!ctid)
169 return Error{Status{RippledError::rpcINVALID_PARAMS}};
170
171 auto const [lgrSeq, txnIdx, netId] = *ctid;
172 // when current network id is available, let us check the network id from parameter
173 if (currentNetId && netId != *currentNetId) {
174 return Error{Status{
175 RippledError::rpcWRONG_NETWORK,
176 fmt::format(
177 "Wrong network. You should submit this request to a node running on NetworkID: {}", netId
178 )
179 }};
180 }
181
182 dbResponse = fetchTxViaCtid(lgrSeq, txnIdx, ctx.yield);
183 } else {
184 dbResponse = sharedPtrBackend_->fetchTransaction(ripple::uint256{input.transaction->c_str()}, ctx.yield);
185 }
186
187 auto output = TxHandler::Output{.apiVersion = ctx.apiVersion};
188
189 if (!dbResponse) {
190 if (rangeSupplied && input.transaction) // ranges not for ctid
191 {
192 auto const range = sharedPtrBackend_->fetchLedgerRange();
193 ASSERT(range.has_value(), "Tx's ledger range must be available");
194
195 auto const searchedAll =
196 range->maxSequence >= *input.maxLedger && range->minSequence <= *input.minLedger;
197 boost::json::object extra;
198 extra["searched_all"] = searchedAll;
199
200 return Error{Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)}};
201 }
202
203 return Error{Status{RippledError::rpcTXN_NOT_FOUND}};
204 }
205
206 auto const [txn, meta] = toExpandedJson(*dbResponse, ctx.apiVersion, NFTokenjson::ENABLE, currentNetId);
207
208 if (!input.binary) {
209 output.tx = txn;
210 output.meta = meta;
211 } else {
212 output.txStr = ripple::strHex(dbResponse->transaction);
213 output.metaStr = ripple::strHex(dbResponse->metadata);
214
215 // input.transaction might be not available, get hash via tx object
216 if (txn.contains(JS(hash)))
217 output.hash = txn.at(JS(hash)).as_string();
218 }
219
220 // append ctid here to mimic rippled behavior
221 auto const txnIdx = boost::json::value_to<uint64_t>(meta.at("TransactionIndex"));
222 if (txnIdx <= 0xFFFFU && dbResponse->ledgerSequence < 0x0FFF'FFFFUL && currentNetId &&
223 *currentNetId <= 0xFFFFU) {
224 output.ctid = rpc::encodeCTID(
225 dbResponse->ledgerSequence, static_cast<uint16_t>(txnIdx), static_cast<uint16_t>(*currentNetId)
226 );
227 }
228
229 output.date = dbResponse->date;
230 output.ledgerIndex = dbResponse->ledgerSequence;
231
232 // fetch ledger hash
233 if (ctx.apiVersion > 1u)
234 output.ledgerHeader = sharedPtrBackend_->fetchLedgerBySequence(dbResponse->ledgerSequence, ctx.yield);
235
236 return output;
237 }
238
239private:
240 std::optional<data::TransactionAndMetadata>
241 fetchTxViaCtid(uint32_t ledgerSeq, uint32_t txId, boost::asio::yield_context yield) const
242 {
243 auto const txs = sharedPtrBackend_->fetchAllTransactionsInLedger(ledgerSeq, yield);
244
245 for (auto const& tx : txs) {
246 auto const [txn, meta] = deserializeTxPlusMeta(tx, ledgerSeq);
247
248 if (meta->getIndex() == txId)
249 return tx;
250 }
251
252 return std::nullopt;
253 }
254
255 friend void
256 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
257 {
258 auto const getJsonV1 = [&]() {
259 auto obj = boost::json::object{};
260
261 if (output.tx) {
262 obj = *output.tx;
263 obj[JS(meta)] = *output.meta;
264 } else {
265 obj[JS(meta)] = *output.metaStr;
266 obj[JS(tx)] = *output.txStr;
267 obj[JS(hash)] = output.hash;
268 }
269
270 obj[JS(validated)] = output.validated;
271 obj[JS(date)] = output.date;
272 obj[JS(ledger_index)] = output.ledgerIndex;
273 obj[JS(inLedger)] = output.ledgerIndex;
274 return obj;
275 };
276
277 auto const getJsonV2 = [&]() {
278 auto obj = boost::json::object{};
279
280 if (output.tx) {
281 obj[JS(tx_json)] = *output.tx;
282 obj[JS(tx_json)].as_object()[JS(date)] = output.date;
283 if (output.ctid)
284 obj[JS(tx_json)].as_object()[JS(ctid)] = *output.ctid;
285
286 obj[JS(tx_json)].as_object()[JS(ledger_index)] = output.ledgerIndex;
287 // move hash from tx_json to root
288 if (obj[JS(tx_json)].as_object().contains(JS(hash))) {
289 obj[JS(hash)] = obj[JS(tx_json)].as_object()[JS(hash)];
290 obj[JS(tx_json)].as_object().erase(JS(hash));
291 }
292 obj[JS(meta)] = *output.meta;
293 } else {
294 obj[JS(meta_blob)] = *output.metaStr;
295 obj[JS(tx_blob)] = *output.txStr;
296 obj[JS(hash)] = output.hash;
297 }
298
299 obj[JS(validated)] = output.validated;
300 obj[JS(ledger_index)] = output.ledgerIndex;
301
302 if (output.ledgerHeader) {
303 obj[JS(ledger_hash)] = ripple::strHex(output.ledgerHeader->hash);
304 obj[JS(close_time_iso)] = ripple::to_string_iso(output.ledgerHeader->closeTime);
305 }
306 return obj;
307 };
308
309 auto obj = output.apiVersion > 1u ? getJsonV2() : getJsonV1();
310
311 if (output.ctid)
312 obj[JS(ctid)] = *output.ctid;
313
314 jv = std::move(obj);
315 }
316
317 friend Input
318 tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
319 {
320 auto input = TxHandler::Input{};
321 auto const& jsonObject = jv.as_object();
322
323 if (jsonObject.contains(JS(transaction)))
324 input.transaction = boost::json::value_to<std::string>(jv.at(JS(transaction)));
325
326 if (jsonObject.contains(JS(ctid))) {
327 input.ctid = boost::json::value_to<std::string>(jv.at(JS(ctid)));
328 input.ctid = util::toUpper(*input.ctid);
329 }
330
331 if (jsonObject.contains(JS(binary)))
332 input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
333
334 if (jsonObject.contains(JS(min_ledger)))
335 input.minLedger = jv.at(JS(min_ledger)).as_int64();
336
337 if (jsonObject.contains(JS(max_ledger)))
338 input.maxLedger = jv.at(JS(max_ledger)).as_int64();
339
340 return input;
341 }
342};
343
344} // namespace rpc
The tx method retrieves information on a single transaction, by its identifying hash.
Definition Tx.hpp:61
TxHandler(std::shared_ptr< BackendInterface > const &sharedPtrBackend, std::shared_ptr< etlng::ETLServiceInterface const > const &etl)
Construct a new TxHandler object.
Definition Tx.hpp:104
Result process(Input input, Context const &ctx) const
Process the Tx command.
Definition Tx.hpp:141
static RpcSpecConstRef spec(uint32_t apiVersion)
Returns the API specification for the command.
Definition Tx.hpp:119
This namespace contains everything to do with the ETL and ETL sources.
Definition CacheLoader.hpp:39
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:210
RpcSpec const & RpcSpecConstRef
An alias for a const reference to RpcSpec.
Definition Specs.hpp:145
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:709
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
std::string toUpper(std::string str)
Convert a string to uppercase.
Definition JsonUtils.hpp:54
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:98
A status returned from any RPC handler.
Definition Errors.hpp:82
A struct to hold the input data for the command.
Definition Tx.hpp:88
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:530
Validates that the type of the value is one of the given types.
Definition Validators.hpp:142