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> const& sharedPtrBackend,
111 std::shared_ptr<etl::ETLServiceInterface const> const& etl
112 )
113 : sharedPtrBackend_(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 return output;
251 }
252
253private:
254 std::optional<data::TransactionAndMetadata>
255 fetchTxViaCtid(uint32_t ledgerSeq, uint32_t txId, boost::asio::yield_context yield) const
256 {
257 auto const txs = sharedPtrBackend_->fetchAllTransactionsInLedger(ledgerSeq, yield);
258
259 for (auto const& tx : txs) {
260 auto const [txn, meta] = deserializeTxPlusMeta(tx, ledgerSeq);
261
262 if (meta->getIndex() == txId)
263 return tx;
264 }
265
266 return std::nullopt;
267 }
268
269 friend void
270 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
271 {
272 auto const getJsonV1 = [&]() {
273 auto obj = boost::json::object{};
274
275 if (output.tx) {
276 obj = *output.tx;
277 obj[JS(meta)] = *output.meta;
278 } else {
279 obj[JS(meta)] = *output.metaStr;
280 obj[JS(tx)] = *output.txStr;
281 obj[JS(hash)] = output.hash;
282 }
283
284 obj[JS(validated)] = output.validated;
285 obj[JS(date)] = output.date;
286 obj[JS(ledger_index)] = output.ledgerIndex;
287 obj[JS(inLedger)] = output.ledgerIndex;
288 return obj;
289 };
290
291 auto const getJsonV2 = [&]() {
292 auto obj = boost::json::object{};
293
294 if (output.tx) {
295 obj[JS(tx_json)] = *output.tx;
296 obj[JS(tx_json)].as_object()[JS(date)] = output.date;
297 if (output.ctid)
298 obj[JS(tx_json)].as_object()[JS(ctid)] = *output.ctid;
299
300 obj[JS(tx_json)].as_object()[JS(ledger_index)] = output.ledgerIndex;
301 // move hash from tx_json to root
302 if (obj[JS(tx_json)].as_object().contains(JS(hash))) {
303 obj[JS(hash)] = obj[JS(tx_json)].as_object()[JS(hash)];
304 obj[JS(tx_json)].as_object().erase(JS(hash));
305 }
306 obj[JS(meta)] = *output.meta;
307 } else {
308 obj[JS(meta_blob)] = *output.metaStr;
309 obj[JS(tx_blob)] = *output.txStr;
310 obj[JS(hash)] = output.hash;
311 }
312
313 obj[JS(validated)] = output.validated;
314 obj[JS(ledger_index)] = output.ledgerIndex;
315
316 if (output.ledgerHeader) {
317 obj[JS(ledger_hash)] = ripple::strHex(output.ledgerHeader->hash);
318 obj[JS(close_time_iso)] = ripple::to_string_iso(output.ledgerHeader->closeTime);
319 }
320 return obj;
321 };
322
323 auto obj = output.apiVersion > 1u ? getJsonV2() : getJsonV1();
324
325 if (output.ctid)
326 obj[JS(ctid)] = *output.ctid;
327
328 jv = std::move(obj);
329 }
330
331 friend Input
332 tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
333 {
334 auto input = TxHandler::Input{};
335 auto const& jsonObject = jv.as_object();
336
337 if (jsonObject.contains(JS(transaction)))
338 input.transaction = boost::json::value_to<std::string>(jv.at(JS(transaction)));
339
340 if (jsonObject.contains(JS(ctid))) {
341 input.ctid = boost::json::value_to<std::string>(jv.at(JS(ctid)));
342 input.ctid = util::toUpper(*input.ctid);
343 }
344
345 if (jsonObject.contains(JS(binary)))
346 input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
347
348 if (jsonObject.contains(JS(min_ledger)))
349 input.minLedger = util::integralValueAs<uint32_t>(jv.at(JS(min_ledger)));
350
351 if (jsonObject.contains(JS(max_ledger)))
352 input.maxLedger = util::integralValueAs<uint32_t>(jv.at(JS(max_ledger)));
353
354 return input;
355 }
356};
357
358} // namespace rpc
TxHandler(std::shared_ptr< BackendInterface > const &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:150
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:102
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:568
Validates that the type of the value is one of the given types.
Definition Validators.hpp:146