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 <xrpl/basics/base_uint.h>
41#include <xrpl/basics/chrono.h>
42#include <xrpl/basics/strHex.h>
43#include <xrpl/protocol/ErrorCodes.h>
44#include <xrpl/protocol/LedgerHeader.h>
45#include <xrpl/protocol/jss.h>
46
47#include <cstdint>
48#include <memory>
49#include <optional>
50#include <string>
51#include <utility>
52
53namespace rpc {
54
60class TxHandler {
61 std::shared_ptr<BackendInterface> sharedPtrBackend_;
62 std::shared_ptr<etlng::ETLServiceInterface const> etl_;
63
64public:
68 struct Output {
69 uint32_t date = 0u;
70 std::string hash = {}; // NOLINT(readability-redundant-member-init)
71 uint32_t ledgerIndex = 0u;
72 std::optional<boost::json::object> meta = std::nullopt; // NOLINT(readability-redundant-member-init)
73 std::optional<boost::json::object> tx = std::nullopt; // NOLINT(readability-redundant-member-init)
74 std::optional<std::string> metaStr = std::nullopt; // NOLINT(readability-redundant-member-init)
75 std::optional<std::string> txStr = std::nullopt; // NOLINT(readability-redundant-member-init)
76 std::optional<std::string> ctid =
77 std::nullopt; // NOLINT(readability-redundant-member-init) ctid when binary=true
78 std::optional<ripple::LedgerHeader> ledgerHeader =
79 std::nullopt; // NOLINT(readability-redundant-member-init) ledger hash when apiVersion >= 2
80 uint32_t apiVersion = 0u;
81 bool validated = true;
82 };
83
87 struct Input {
88 std::optional<std::string> transaction;
89 std::optional<std::string> ctid;
90 bool binary = false;
91 std::optional<uint32_t> minLedger;
92 std::optional<uint32_t> maxLedger;
93 };
94
95 using Result = HandlerReturnType<Output>;
96
104 std::shared_ptr<BackendInterface> const& sharedPtrBackend,
105 std::shared_ptr<etlng::ETLServiceInterface const> const& etl
106 )
107 : sharedPtrBackend_(sharedPtrBackend), etl_(etl)
108 {
109 }
110
117 static RpcSpecConstRef
118 spec(uint32_t apiVersion)
119 {
120 static RpcSpec const kRPC_SPEC_FOR_V1 = {
122 {JS(min_ledger), validation::Type<uint32_t>{}},
123 {JS(max_ledger), validation::Type<uint32_t>{}},
124 {JS(ctid), validation::Type<std::string>{}},
125 };
126
127 static auto const kRPC_SPEC = RpcSpec{kRPC_SPEC_FOR_V1, {{JS(binary), validation::Type<bool>{}}}};
128
129 return apiVersion == 1 ? kRPC_SPEC_FOR_V1 : kRPC_SPEC;
130 }
131
139 Result
140 process(Input input, Context const& ctx) const
141 {
142 if (input.ctid && input.transaction) // ambiguous identifier
143 return Error{Status{RippledError::rpcINVALID_PARAMS}};
144
145 if (!input.ctid && !input.transaction) // at least one identifier must be supplied
146 return Error{Status{RippledError::rpcINVALID_PARAMS}};
147
148 static constexpr auto kMAX_LEDGER_RANGE = 1000u;
149 auto const rangeSupplied = input.minLedger && input.maxLedger;
150
151 if (rangeSupplied) {
152 if (*input.minLedger > *input.maxLedger)
153 return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
154
155 if (*input.maxLedger - *input.minLedger > kMAX_LEDGER_RANGE)
156 return Error{Status{RippledError::rpcEXCESSIVE_LGR_RANGE}};
157 }
158
159 std::optional<uint32_t> currentNetId = std::nullopt;
160 if (auto const& etlState = etl_->getETLState(); etlState.has_value())
161 currentNetId = etlState->networkID;
162
163 std::optional<data::TransactionAndMetadata> dbResponse;
164
165 if (input.ctid) {
166 auto const ctid = rpc::decodeCTID(*input.ctid);
167 if (!ctid)
168 return Error{Status{RippledError::rpcINVALID_PARAMS}};
169
170 auto const [lgrSeq, txnIdx, netId] = *ctid;
171 // when current network id is available, let us check the network id from parameter
172 if (currentNetId && netId != *currentNetId) {
173 return Error{Status{
174 RippledError::rpcWRONG_NETWORK,
175 fmt::format(
176 "Wrong network. You should submit this request to a node running on NetworkID: {}", netId
177 )
178 }};
179 }
180
181 dbResponse = fetchTxViaCtid(lgrSeq, txnIdx, ctx.yield);
182 } else {
183 dbResponse = sharedPtrBackend_->fetchTransaction(ripple::uint256{input.transaction->c_str()}, ctx.yield);
184 }
185
186 auto output = TxHandler::Output{.apiVersion = ctx.apiVersion};
187
188 if (!dbResponse) {
189 if (rangeSupplied && input.transaction) // ranges not for ctid
190 {
191 auto const range = sharedPtrBackend_->fetchLedgerRange();
192 ASSERT(range.has_value(), "Tx's ledger range must be available");
193
194 auto const searchedAll =
195 range->maxSequence >= *input.maxLedger && range->minSequence <= *input.minLedger;
196 boost::json::object extra;
197 extra["searched_all"] = searchedAll;
198
199 return Error{Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)}};
200 }
201
202 return Error{Status{RippledError::rpcTXN_NOT_FOUND}};
203 }
204
205 auto const [txn, meta] = toExpandedJson(*dbResponse, ctx.apiVersion, NFTokenjson::ENABLE, currentNetId);
206
207 if (!input.binary) {
208 output.tx = txn;
209 output.meta = meta;
210 } else {
211 output.txStr = ripple::strHex(dbResponse->transaction);
212 output.metaStr = ripple::strHex(dbResponse->metadata);
213
214 // input.transaction might be not available, get hash via tx object
215 if (txn.contains(JS(hash)))
216 output.hash = txn.at(JS(hash)).as_string();
217 }
218
219 // append ctid here to mimic rippled behavior
220 auto const txnIdx = boost::json::value_to<uint64_t>(meta.at("TransactionIndex"));
221 if (txnIdx <= 0xFFFFU && dbResponse->ledgerSequence < 0x0FFF'FFFFUL && currentNetId &&
222 *currentNetId <= 0xFFFFU) {
223 output.ctid = rpc::encodeCTID(
224 dbResponse->ledgerSequence, static_cast<uint16_t>(txnIdx), static_cast<uint16_t>(*currentNetId)
225 );
226 }
227
228 output.date = dbResponse->date;
229 output.ledgerIndex = dbResponse->ledgerSequence;
230
231 // fetch ledger hash
232 if (ctx.apiVersion > 1u)
233 output.ledgerHeader = sharedPtrBackend_->fetchLedgerBySequence(dbResponse->ledgerSequence, ctx.yield);
234
235 return output;
236 }
237
238private:
239 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 = jv.at(JS(min_ledger)).as_int64();
335
336 if (jsonObject.contains(JS(max_ledger)))
337 input.maxLedger = jv.at(JS(max_ledger)).as_int64();
338
339 return input;
340 }
341};
342
343} // namespace rpc
The tx method retrieves information on a single transaction, by its identifying hash.
Definition Tx.hpp:60
TxHandler(std::shared_ptr< BackendInterface > const &sharedPtrBackend, std::shared_ptr< etlng::ETLServiceInterface const > const &etl)
Construct a new TxHandler object.
Definition Tx.hpp:103
Result process(Input input, Context const &ctx) const
Process the Tx command.
Definition Tx.hpp:140
static RpcSpecConstRef spec(uint32_t apiVersion)
Returns the API specification for the command.
Definition Tx.hpp:118
This namespace contains everything to do with the ETL and ETL sources.
Definition CacheLoader.hpp:37
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:87
A struct to hold the output data of the command.
Definition Tx.hpp:68
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