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/ETLService.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/strHex.h>
42#include <xrpl/protocol/ErrorCodes.h>
43#include <xrpl/protocol/LedgerHeader.h>
44#include <xrpl/protocol/jss.h>
45
46#include <cstdint>
47#include <memory>
48#include <optional>
49#include <string>
50#include <utility>
51
52namespace rpc {
53
59template <typename ETLServiceType>
61 std::shared_ptr<BackendInterface> sharedPtrBackend_;
62 std::shared_ptr<ETLServiceType 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<ETLServiceType 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 = BaseTxHandler::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 // append ctid here to mimic rippled 1.12 behavior: return ctid even binary=true
219 // rippled will change it in the future, ctid should be part of tx json which not available in binary
220 // mode
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
230 output.date = dbResponse->date;
231 output.ledgerIndex = dbResponse->ledgerSequence;
232
233 // fetch ledger hash
234 if (ctx.apiVersion > 1u)
235 output.ledgerHeader = sharedPtrBackend_->fetchLedgerBySequence(dbResponse->ledgerSequence, ctx.yield);
236
237 return output;
238 }
239
240private:
241 std::optional<data::TransactionAndMetadata>
242 fetchTxViaCtid(uint32_t ledgerSeq, uint32_t txId, boost::asio::yield_context yield) const
243 {
244 auto const txs = sharedPtrBackend_->fetchAllTransactionsInLedger(ledgerSeq, yield);
245
246 for (auto const& tx : txs) {
247 auto const [txn, meta] = deserializeTxPlusMeta(tx, ledgerSeq);
248
249 if (meta->getIndex() == txId)
250 return tx;
251 }
252
253 return std::nullopt;
254 }
255
256 friend void
257 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
258 {
259 auto const getJsonV1 = [&]() {
260 auto obj = boost::json::object{};
261
262 if (output.tx) {
263 obj = *output.tx;
264 obj[JS(meta)] = *output.meta;
265 } else {
266 obj[JS(meta)] = *output.metaStr;
267 obj[JS(tx)] = *output.txStr;
268 obj[JS(hash)] = output.hash;
269 }
270
271 obj[JS(validated)] = output.validated;
272 obj[JS(date)] = output.date;
273 obj[JS(ledger_index)] = output.ledgerIndex;
274 obj[JS(inLedger)] = output.ledgerIndex;
275 return obj;
276 };
277
278 auto const getJsonV2 = [&]() {
279 auto obj = boost::json::object{};
280
281 if (output.tx) {
282 obj[JS(tx_json)] = *output.tx;
283 obj[JS(tx_json)].as_object()[JS(date)] = output.date;
284 obj[JS(tx_json)].as_object()[JS(ledger_index)] = output.ledgerIndex;
285 // move ctid from tx_json to root
286 if (obj[JS(tx_json)].as_object().contains(JS(ctid))) {
287 obj[JS(ctid)] = obj[JS(tx_json)].as_object()[JS(ctid)];
288 obj[JS(tx_json)].as_object().erase(JS(ctid));
289 }
290 // move hash from tx_json to root
291 if (obj[JS(tx_json)].as_object().contains(JS(hash))) {
292 obj[JS(hash)] = obj[JS(tx_json)].as_object()[JS(hash)];
293 obj[JS(tx_json)].as_object().erase(JS(hash));
294 }
295 obj[JS(meta)] = *output.meta;
296 } else {
297 obj[JS(meta_blob)] = *output.metaStr;
298 obj[JS(tx_blob)] = *output.txStr;
299 obj[JS(hash)] = output.hash;
300 }
301
302 obj[JS(validated)] = output.validated;
303 obj[JS(ledger_index)] = output.ledgerIndex;
304
305 if (output.ledgerHeader) {
306 obj[JS(ledger_hash)] = ripple::strHex(output.ledgerHeader->hash);
307 obj[JS(close_time_iso)] = ripple::to_string_iso(output.ledgerHeader->closeTime);
308 }
309 return obj;
310 };
311
312 auto obj = output.apiVersion > 1u ? getJsonV2() : getJsonV1();
313
314 if (output.ctid)
315 obj[JS(ctid)] = *output.ctid;
316
317 jv = std::move(obj);
318 }
319
320 friend Input
321 tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
322 {
323 auto input = BaseTxHandler::Input{};
324 auto const& jsonObject = jv.as_object();
325
326 if (jsonObject.contains(JS(transaction)))
327 input.transaction = boost::json::value_to<std::string>(jv.at(JS(transaction)));
328
329 if (jsonObject.contains(JS(ctid))) {
330 input.ctid = boost::json::value_to<std::string>(jv.at(JS(ctid)));
331 input.ctid = util::toUpper(*input.ctid);
332 }
333
334 if (jsonObject.contains(JS(binary)))
335 input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
336
337 if (jsonObject.contains(JS(min_ledger)))
338 input.minLedger = jv.at(JS(min_ledger)).as_int64();
339
340 if (jsonObject.contains(JS(max_ledger)))
341 input.maxLedger = jv.at(JS(max_ledger)).as_int64();
342
343 return input;
344 }
345};
346
353} // namespace rpc
Contains common functionality for handling the tx command.
Definition Tx.hpp:60
BaseTxHandler(std::shared_ptr< BackendInterface > const &sharedPtrBackend, std::shared_ptr< ETLServiceType const > const &etl)
Construct a new BaseTxHandler object.
Definition Tx.hpp:103
static RpcSpecConstRef spec(uint32_t apiVersion)
Returns the API specification for the command.
Definition Tx.hpp:118
Result process(Input input, Context const &ctx) const
Process the Tx command.
Definition Tx.hpp:140
This namespace contains everything to do with the ETL and ETL sources.
Definition CacheLoader.hpp:36
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:36
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:207
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:287
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:614
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:251
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
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
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
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