Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
Tx.hpp
1#pragma once
2
3#include "data/BackendInterface.hpp"
4#include "data/Types.hpp"
5#include "etl/ETLServiceInterface.hpp"
6#include "rpc/JS.hpp"
7#include "rpc/RPCHelpers.hpp"
8#include "rpc/common/Types.hpp"
9#include "util/Assert.hpp"
10
11#include <boost/asio/spawn.hpp>
12#include <boost/json/conversion.hpp>
13#include <boost/json/object.hpp>
14#include <boost/json/value.hpp>
15#include <boost/json/value_to.hpp>
16#include <fmt/format.h>
17#include <rpcspec/Errors.hpp>
18#include <rpcspec/HandlerFor.hpp>
19#include <rpcspec/handlers/tx/Types.hpp>
20#include <xrpl/basics/chrono.h>
21#include <xrpl/basics/strHex.h>
22#include <xrpl/protocol/LedgerHeader.h>
23#include <xrpl/protocol/jss.h>
24
25#include <cstdint>
26#include <memory>
27#include <optional>
28#include <string>
29#include <utility>
30
31namespace rpc {
32
38class TxHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::tx::Input> {
39 std::shared_ptr<BackendInterface> sharedPtrBackend_;
40 std::shared_ptr<etl::ETLServiceInterface const> etl_;
41
42public:
46 struct Output {
47 uint32_t date = 0u;
48 std::string hash = {}; // NOLINT(readability-redundant-member-init)
49 uint32_t ledgerIndex = 0u;
50 std::optional<boost::json::object> meta =
51 std::nullopt; // NOLINT(readability-redundant-member-init)
52 std::optional<boost::json::object> tx =
53 std::nullopt; // NOLINT(readability-redundant-member-init)
54 std::optional<std::string> metaStr =
55 std::nullopt; // NOLINT(readability-redundant-member-init)
56 std::optional<std::string> txStr =
57 std::nullopt; // NOLINT(readability-redundant-member-init)
58 std::optional<std::string> ctid =
59 std::nullopt; // NOLINT(readability-redundant-member-init) ctid when binary=true
60 std::optional<xrpl::LedgerHeader> ledgerHeader =
61 std::nullopt; // NOLINT(readability-redundant-member-init) ledger hash when apiVersion
62 // >= 2
63 uint32_t apiVersion = 0u;
64 bool validated = true;
65 };
66
67 using Result = HandlerReturnType<Output>;
68
76 std::shared_ptr<BackendInterface> sharedPtrBackend,
77 std::shared_ptr<etl::ETLServiceInterface const> const& etl
78 )
79 : sharedPtrBackend_(std::move(sharedPtrBackend)), etl_(etl)
80 {
81 }
82
90 [[nodiscard]] Result
91 process(Input const& input, Context const& ctx) const
92 {
93 if (input.ctid && input.transaction) // ambiguous identifier
94 return Error{Status{RippledError::RpcInvalidParams}};
95
96 if (!input.ctid && !input.transaction) // at least one identifier must be supplied
97 return Error{Status{RippledError::RpcInvalidParams}};
98
99 static constexpr auto kMaxLedgerRange = 1000u;
100 auto const rangeSupplied = input.minLedger && input.maxLedger;
101
102 if (rangeSupplied) {
103 if (*input.minLedger > *input.maxLedger)
104 return Error{Status{RippledError::RpcInvalidLgrRange}};
105
106 if (*input.maxLedger - *input.minLedger > kMaxLedgerRange)
107 return Error{Status{RippledError::RpcExcessiveLgrRange}};
108 }
109
110 std::optional<uint32_t> currentNetId = std::nullopt;
111 if (auto const& etlState = etl_->getETLState(); etlState.has_value())
112 currentNetId = etlState->networkID;
113
114 std::optional<data::TransactionAndMetadata> dbResponse;
115
116 if (input.ctid) {
117 auto const ctid = rpc::decodeCTID(*input.ctid);
118 if (!ctid)
119 return Error{Status{RippledError::RpcInvalidParams}};
120
121 auto const [lgrSeq, txnIdx, netId] = *ctid;
122 // when current network id is available, let us check the network id from parameter
123 if (currentNetId && netId != *currentNetId) {
124 return Error{Status{
125 RippledError::RpcWrongNetwork,
126 fmt::format(
127 "Wrong network. You should submit this request to a node running on "
128 "NetworkID: {}",
129 netId
130 )
131 }};
132 }
133
134 dbResponse = fetchTxViaCtid(lgrSeq, txnIdx, ctx.yield);
135 } else {
136 dbResponse = sharedPtrBackend_->fetchTransaction(*input.transaction, ctx.yield);
137 }
138
139 auto output = TxHandler::Output{.apiVersion = ctx.apiVersion};
140
141 if (!dbResponse) {
142 if (rangeSupplied && input.transaction) // ranges not for ctid
143 {
144 auto const range = sharedPtrBackend_->fetchLedgerRange();
145 ASSERT(range.has_value(), "Tx's ledger range must be available");
146
147 // NOLINTBEGIN(bugprone-unchecked-optional-access)
148 auto const searchedAll = range->maxSequence >= *input.maxLedger &&
149 range->minSequence <= *input.minLedger;
150 // NOLINTEND(bugprone-unchecked-optional-access)
151
152 boost::json::object extra;
153 extra["searched_all"] = searchedAll;
154
155 return Error{Status{RippledError::RpcTxnNotFound, std::move(extra)}};
156 }
157
158 return Error{Status{RippledError::RpcTxnNotFound}};
159 }
160
161 auto const [txn, meta] =
162 toExpandedJson(*dbResponse, ctx.apiVersion, NFTokenjson::ENABLE, currentNetId);
163
164 if (!input.binary) {
165 output.tx = txn;
166 output.meta = meta;
167 } else {
168 output.txStr = xrpl::strHex(dbResponse->transaction);
169 output.metaStr = xrpl::strHex(dbResponse->metadata);
170
171 // input.transaction might be not available, get hash via tx object
172 if (txn.contains(JS(hash)))
173 output.hash = txn.at(JS(hash)).as_string();
174 }
175
176 // append ctid here to mimic rippled behavior
177 auto const txnIdx = boost::json::value_to<uint64_t>(meta.at("TransactionIndex"));
178 if (txnIdx <= 0xFFFFU && dbResponse->ledgerSequence < 0x0FFF'FFFFUL && currentNetId &&
179 *currentNetId <= 0xFFFFU) {
180 output.ctid = rpc::encodeCTID(
181 dbResponse->ledgerSequence,
182 static_cast<uint16_t>(txnIdx),
183 static_cast<uint16_t>(*currentNetId)
184 );
185 }
186
187 output.date = dbResponse->date;
188 output.ledgerIndex = dbResponse->ledgerSequence;
189
190 // fetch ledger hash
191 if (ctx.apiVersion > 1u) {
192 output.ledgerHeader =
193 sharedPtrBackend_->fetchLedgerBySequence(dbResponse->ledgerSequence, ctx.yield);
194 }
195
196 return output;
197 }
198
199private:
200 [[nodiscard]] std::optional<data::TransactionAndMetadata>
201 fetchTxViaCtid(uint32_t ledgerSeq, uint32_t txId, boost::asio::yield_context yield) const
202 {
203 auto const txs = sharedPtrBackend_->fetchAllTransactionsInLedger(ledgerSeq, yield);
204
205 for (auto const& tx : txs) {
206 auto const [txn, meta] = deserializeTxPlusMeta(tx, ledgerSeq);
207
208 if (meta->getIndex() == txId)
209 return tx;
210 }
211
212 return std::nullopt;
213 }
214
215 friend void
216 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
217 {
218 auto const getJsonV1 = [&]() {
219 auto obj = boost::json::object{};
220
221 if (output.tx) {
222 obj = *output.tx;
223 obj[JS(meta)] = *output.meta;
224 } else {
225 obj[JS(meta)] = *output.metaStr;
226 obj[JS(tx)] = *output.txStr;
227 obj[JS(hash)] = output.hash;
228 }
229
230 obj[JS(validated)] = output.validated;
231 obj[JS(date)] = output.date;
232 obj[JS(ledger_index)] = output.ledgerIndex;
233 obj[JS(inLedger)] = output.ledgerIndex;
234 return obj;
235 };
236
237 auto const getJsonV2 = [&]() {
238 auto obj = boost::json::object{};
239
240 if (output.tx) {
241 obj[JS(tx_json)] = *output.tx;
242 obj[JS(tx_json)].as_object()[JS(date)] = output.date;
243 if (output.ctid)
244 obj[JS(tx_json)].as_object()[JS(ctid)] = *output.ctid;
245
246 obj[JS(tx_json)].as_object()[JS(ledger_index)] = output.ledgerIndex;
247 // move hash from tx_json to root
248 if (obj[JS(tx_json)].as_object().contains(JS(hash))) {
249 obj[JS(hash)] = obj[JS(tx_json)].as_object()[JS(hash)];
250 obj[JS(tx_json)].as_object().erase(JS(hash));
251 }
252 obj[JS(meta)] = *output.meta;
253 } else {
254 obj[JS(meta_blob)] = *output.metaStr;
255 obj[JS(tx_blob)] = *output.txStr;
256 obj[JS(hash)] = output.hash;
257 }
258
259 obj[JS(validated)] = output.validated;
260 obj[JS(ledger_index)] = output.ledgerIndex;
261
262 if (output.ledgerHeader) {
263 obj[JS(ledger_hash)] = xrpl::strHex(output.ledgerHeader->hash);
264 obj[JS(close_time_iso)] = xrpl::toStringIso(output.ledgerHeader->closeTime);
265 }
266 return obj;
267 };
268
269 auto obj = output.apiVersion > 1u ? getJsonV2() : getJsonV1();
270
271 if (output.ctid)
272 obj[JS(ctid)] = *output.ctid;
273
274 jv = std::move(obj);
275 }
276};
277
278} // namespace rpc
TxHandler(std::shared_ptr< BackendInterface > sharedPtrBackend, std::shared_ptr< etl::ETLServiceInterface const > const &etl)
Construct a new TxHandler object.
Definition Tx.hpp:75
Result process(Input const &input, Context const &ctx) const
Process the Tx command.
Definition Tx.hpp:91
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:17
std::expected< OutputType, Status > HandlerReturnType
Return type for each individual handler.
Definition Types.hpp:61
std::optional< std::string > encodeCTID(uint32_t ledgerSeq, uint16_t txnIndex, uint16_t networkId) noexcept
Encode CTID as string.
Definition RPCHelpers.cpp:272
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:793
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:235
std::unexpected< Status > Error
The type that represents just the error part of MaybeError.
Definition Types.hpp:55
std::pair< std::shared_ptr< xrpl::STTx const >, std::shared_ptr< xrpl::STObject const > > deserializeTxPlusMeta(data::TransactionAndMetadata const &blobs)
Deserialize a TransactionAndMetadata into a pair of STTx and STObject.
Definition RPCHelpers.cpp:186
Context of an RPC call.
Definition Types.hpp:98
Result type used to return responses or error statuses to the Webserver subsystem.
Definition Types.hpp:109
A struct to hold the output data of the command.
Definition Tx.hpp:46