rippled
Tx.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2014 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or 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 #include <ripple/app/ledger/LedgerMaster.h>
21 #include <ripple/app/ledger/TransactionMaster.h>
22 #include <ripple/app/misc/DeliverMax.h>
23 #include <ripple/app/misc/NetworkOPs.h>
24 #include <ripple/app/misc/Transaction.h>
25 #include <ripple/app/rdb/RelationalDatabase.h>
26 #include <ripple/basics/ToString.h>
27 #include <ripple/net/RPCErr.h>
28 #include <ripple/protocol/ErrorCodes.h>
29 #include <ripple/protocol/NFTSyntheticSerializer.h>
30 #include <ripple/protocol/jss.h>
31 #include <ripple/rpc/CTID.h>
32 #include <ripple/rpc/Context.h>
33 #include <ripple/rpc/DeliveredAmount.h>
34 #include <ripple/rpc/GRPCHandlers.h>
35 #include <ripple/rpc/impl/RPCHelpers.h>
36 
37 #include <charconv>
38 #include <regex>
39 
40 namespace ripple {
41 
42 static bool
43 isValidated(LedgerMaster& ledgerMaster, std::uint32_t seq, uint256 const& hash)
44 {
45  if (!ledgerMaster.haveLedger(seq))
46  return false;
47 
48  if (seq > ledgerMaster.getValidatedLedger()->info().seq)
49  return false;
50 
51  return ledgerMaster.getHashBySeq(seq) == hash;
52 }
53 
54 struct TxResult
55 {
58  bool validated = false;
63 };
64 
65 struct TxArgs
66 {
69  bool binary = false;
71 };
72 
74 doTxPostgres(RPC::Context& context, TxArgs const& args)
75 {
76  if (!context.app.config().reporting())
77  {
78  assert(false);
79  Throw<std::runtime_error>(
80  "Called doTxPostgres yet not in reporting mode");
81  }
82 
83  TxResult res;
85 
86  if (!args.hash)
87  return {
88  res,
89  {rpcNOT_IMPL,
90  "Use of CTIDs on reporting mode is not currently supported."}};
91 
92  JLOG(context.j.debug()) << "Fetching from postgres";
93  Transaction::Locator locator =
94  Transaction::locate(*(args.hash), context.app);
95 
97  pair;
98  // database returned the nodestore hash. Fetch the txn directly from the
99  // nodestore. Don't traverse the transaction SHAMap
100  if (locator.isFound())
101  {
102  auto start = std::chrono::system_clock::now();
103  // The second argument of fetch is ignored when not using shards
104  if (auto obj = context.app.getNodeFamily().db().fetchNodeObject(
105  locator.getNodestoreHash(), locator.getLedgerSequence()))
106  {
107  auto node = SHAMapTreeNode::makeFromPrefix(
108  makeSlice(obj->getData()),
109  SHAMapHash{locator.getNodestoreHash()});
110  if (!node)
111  {
112  assert(false);
113  return {res, {rpcINTERNAL, "Error making SHAMap node"}};
114  }
115  auto item = (static_cast<SHAMapLeafNode*>(node.get()))->peekItem();
116  if (!item)
117  {
118  assert(false);
119  return {res, {rpcINTERNAL, "Error reading SHAMap node"}};
120  }
121 
122  auto [sttx, meta] = deserializeTxPlusMeta(*item);
123  JLOG(context.j.debug()) << "Successfully fetched from db";
124 
125  if (!sttx || !meta)
126  {
127  assert(false);
128  return {res, {rpcINTERNAL, "Error deserializing SHAMap node"}};
129  }
130  std::string reason;
131  res.txn = std::make_shared<Transaction>(sttx, reason, context.app);
132  res.txn->setLedger(locator.getLedgerSequence());
133  res.txn->setStatus(COMMITTED);
134  if (args.binary)
135  {
136  SerialIter it(item->slice());
137  it.skip(it.getVLDataLength()); // skip transaction
138  Blob blob = it.getVL();
139  res.meta = std::move(blob);
140  }
141  else
142  {
143  res.meta = std::make_shared<TxMeta>(
144  *(args.hash), res.txn->getLedger(), *meta);
145  }
146  res.validated = true;
147 
148  auto const ledgerInfo =
150  locator.getLedgerSequence());
151  res.closeTime = ledgerInfo->closeTime;
152  res.ledgerHash = ledgerInfo->hash;
153 
154  return {res, rpcSUCCESS};
155  }
156  else
157  {
158  JLOG(context.j.error()) << "Failed to fetch from db";
159  assert(false);
160  return {res, {rpcINTERNAL, "Containing SHAMap node not found"}};
161  }
162  auto end = std::chrono::system_clock::now();
163  JLOG(context.j.debug()) << "tx flat fetch time : "
164  << ((end - start).count() / 1000000000.0);
165  }
166  // database did not find the transaction, and returned the ledger range
167  // that was searched
168  else
169  {
170  if (args.ledgerRange)
171  {
172  auto range = locator.getLedgerRangeSearched();
173  auto min = args.ledgerRange->first;
174  auto max = args.ledgerRange->second;
175  if (min >= range.lower() && max <= range.upper())
176  {
178  }
179  else
180  {
182  }
183  }
184  return {res, rpcTXN_NOT_FOUND};
185  }
186  // database didn't return anything. This shouldn't happen
187  assert(false);
188  return {res, {rpcINTERNAL, "unexpected Postgres response"}};
189 }
190 
192 doTxHelp(RPC::Context& context, TxArgs args)
193 {
194  if (context.app.config().reporting())
195  return doTxPostgres(context, args);
196  TxResult result;
197 
199 
200  if (args.ledgerRange)
201  {
202  constexpr uint16_t MAX_RANGE = 1000;
203 
204  if (args.ledgerRange->second < args.ledgerRange->first)
205  return {result, rpcINVALID_LGR_RANGE};
206 
207  if (args.ledgerRange->second - args.ledgerRange->first > MAX_RANGE)
208  return {result, rpcEXCESSIVE_LGR_RANGE};
209 
211  args.ledgerRange->first, args.ledgerRange->second);
212  }
213 
214  auto ec{rpcSUCCESS};
215 
216  using TxPair =
218 
221 
222  if (args.ctid)
223  {
224  args.hash = context.app.getLedgerMaster().txnIdFromIndex(
225  args.ctid->first, args.ctid->second);
226 
227  if (args.hash)
228  range =
229  ClosedInterval<uint32_t>(args.ctid->first, args.ctid->second);
230  }
231 
232  if (!args.hash)
233  return {result, rpcTXN_NOT_FOUND};
234 
235  if (args.ledgerRange)
236  {
237  v = context.app.getMasterTransaction().fetch(*(args.hash), range, ec);
238  }
239  else
240  {
241  v = context.app.getMasterTransaction().fetch(*(args.hash), ec);
242  }
243 
244  if (auto e = std::get_if<TxSearched>(&v))
245  {
246  result.searchedAll = *e;
247  return {result, rpcTXN_NOT_FOUND};
248  }
249 
250  auto [txn, meta] = std::get<TxPair>(v);
251 
252  if (ec == rpcDB_DESERIALIZATION)
253  {
254  return {result, ec};
255  }
256  if (!txn)
257  {
258  return {result, rpcTXN_NOT_FOUND};
259  }
260 
261  // populate transaction data
262  result.txn = txn;
263  if (txn->getLedger() == 0)
264  {
265  return {result, rpcSUCCESS};
266  }
267 
269  context.ledgerMaster.getLedgerBySeq(txn->getLedger());
270 
271  if (ledger && !ledger->open())
272  result.ledgerHash = ledger->info().hash;
273 
274  if (ledger && meta)
275  {
276  if (args.binary)
277  {
278  result.meta = meta->getAsObject().getSerializer().getData();
279  }
280  else
281  {
282  result.meta = meta;
283  }
284  result.validated = isValidated(
285  context.ledgerMaster, ledger->info().seq, ledger->info().hash);
286  if (result.validated)
287  result.closeTime =
288  context.ledgerMaster.getCloseTimeBySeq(txn->getLedger());
289 
290  // compute outgoing CTID
291  uint32_t lgrSeq = ledger->info().seq;
292  uint32_t txnIdx = meta->getAsObject().getFieldU32(sfTransactionIndex);
293  uint32_t netID = context.app.config().NETWORK_ID;
294 
295  if (txnIdx <= 0xFFFFU && netID < 0xFFFFU && lgrSeq < 0x0FFF'FFFFUL)
296  result.ctid =
297  RPC::encodeCTID(lgrSeq, (uint16_t)txnIdx, (uint16_t)netID);
298  }
299 
300  return {result, rpcSUCCESS};
301 }
302 
303 Json::Value
304 populateJsonResponse(
305  std::pair<TxResult, RPC::Status> const& res,
306  TxArgs const& args,
307  RPC::JsonContext const& context)
308 {
309  Json::Value response;
310  RPC::Status const& error = res.second;
311  TxResult const& result = res.first;
312  // handle errors
313  if (error.toErrorCode() != rpcSUCCESS)
314  {
315  if (error.toErrorCode() == rpcTXN_NOT_FOUND &&
316  result.searchedAll != TxSearched::unknown)
317  {
318  response = Json::Value(Json::objectValue);
319  response[jss::searched_all] =
320  (result.searchedAll == TxSearched::all);
321  error.inject(response);
322  }
323  else
324  {
325  error.inject(response);
326  }
327  }
328  // no errors
329  else if (result.txn)
330  {
331  auto const& sttx = result.txn->getSTransaction();
332  if (context.apiVersion > 1)
333  {
334  constexpr auto optionsJson =
335  JsonOptions::include_date | JsonOptions::disable_API_prior_V2;
336  if (args.binary)
337  response[jss::tx_blob] = result.txn->getJson(optionsJson, true);
338  else
339  {
340  response[jss::tx_json] = result.txn->getJson(optionsJson);
341  RPC::insertDeliverMax(
342  response[jss::tx_json],
343  sttx->getTxnType(),
344  context.apiVersion);
345  }
346 
347  // Note, result.ledgerHash is only set in a closed or validated
348  // ledger - as seen in `doTxHelp` and `doTxPostgres`
349  if (result.ledgerHash)
350  response[jss::ledger_hash] = to_string(*result.ledgerHash);
351 
352  response[jss::hash] = to_string(result.txn->getID());
353  if (result.validated)
354  {
355  response[jss::ledger_index] = result.txn->getLedger();
356  if (result.closeTime)
357  response[jss::close_time_iso] =
358  to_string_iso(*result.closeTime);
359  }
360  }
361  else
362  {
363  response =
364  result.txn->getJson(JsonOptions::include_date, args.binary);
365  if (!args.binary)
366  RPC::insertDeliverMax(
367  response, sttx->getTxnType(), context.apiVersion);
368  }
369 
370  // populate binary metadata
371  if (auto blob = std::get_if<Blob>(&result.meta))
372  {
373  assert(args.binary);
374  auto json_meta =
375  (context.apiVersion > 1 ? jss::meta_blob : jss::meta);
376  response[json_meta] = strHex(makeSlice(*blob));
377  }
378  // populate meta data
379  else if (auto m = std::get_if<std::shared_ptr<TxMeta>>(&result.meta))
380  {
381  auto& meta = *m;
382  if (meta)
383  {
384  response[jss::meta] = meta->getJson(JsonOptions::none);
385  insertDeliveredAmount(
386  response[jss::meta], context, result.txn, *meta);
387  insertNFTSyntheticInJson(response, sttx, *meta);
388  }
389  }
390  response[jss::validated] = result.validated;
391 
392  if (result.ctid)
393  response[jss::ctid] = *(result.ctid);
394  }
395  return response;
396 }
397 
398 Json::Value
399 doTxJson(RPC::JsonContext& context)
400 {
401  if (!context.app.config().useTxTables())
402  return rpcError(rpcNOT_ENABLED);
403 
404  // Deserialize and validate JSON arguments
405 
406  TxArgs args;
407 
408  if (context.params.isMember(jss::transaction) &&
409  context.params.isMember(jss::ctid))
410  // specifying both is ambiguous
411  return rpcError(rpcINVALID_PARAMS);
412 
413  if (context.params.isMember(jss::transaction))
414  {
415  uint256 hash;
416  if (!hash.parseHex(context.params[jss::transaction].asString()))
417  return rpcError(rpcNOT_IMPL);
418  args.hash = hash;
419  }
420  else if (context.params.isMember(jss::ctid))
421  {
422  auto ctid = RPC::decodeCTID(context.params[jss::ctid].asString());
423  if (!ctid)
424  return rpcError(rpcINVALID_PARAMS);
425 
426  auto const [lgr_seq, txn_idx, net_id] = *ctid;
427  if (net_id != context.app.config().NETWORK_ID)
428  {
429  std::stringstream out;
430  out << "Wrong network. You should submit this request to a node "
431  "running on NetworkID: "
432  << net_id;
433  return RPC::make_error(rpcWRONG_NETWORK, out.str());
434  }
435  args.ctid = {lgr_seq, txn_idx};
436  }
437  else
438  return rpcError(rpcINVALID_PARAMS);
439 
440  args.binary = context.params.isMember(jss::binary) &&
441  context.params[jss::binary].asBool();
442 
443  if (context.params.isMember(jss::min_ledger) &&
444  context.params.isMember(jss::max_ledger))
445  {
446  try
447  {
448  args.ledgerRange = std::make_pair(
449  context.params[jss::min_ledger].asUInt(),
450  context.params[jss::max_ledger].asUInt());
451  }
452  catch (...)
453  {
454  // One of the calls to `asUInt ()` failed.
455  return rpcError(rpcINVALID_LGR_RANGE);
456  }
457  }
458 
459  std::pair<TxResult, RPC::Status> res = doTxHelp(context, args);
460  return populateJsonResponse(res, args, context);
461 }
462 
463 } // namespace ripple
ripple::COMMITTED
@ COMMITTED
Definition: Transaction.h:51
ripple::TxArgs::binary
bool binary
Definition: Tx.cpp:69
ripple::Application::getNodeFamily
virtual Family & getNodeFamily()=0
ripple::rpcDB_DESERIALIZATION
@ rpcDB_DESERIALIZATION
Definition: ErrorCodes.h:134
ripple::HashPrefix::ledgerMaster
@ ledgerMaster
ledger master data for signing
ripple::makeSlice
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:241
ripple::TxSearched::unknown
@ unknown
regex
std::string
STL class.
std::shared_ptr< Transaction >
std::pair
ripple::TxSearched::all
@ all
ripple::Transaction::Locator::getNodestoreHash
uint256 const & getNodestoreHash()
Definition: Transaction.h:337
ripple::LedgerMaster
Definition: LedgerMaster.h:70
ripple::TxSearched
TxSearched
Definition: Transaction.h:58
charconv
std::vector< unsigned char >
ripple::TxResult::closeTime
std::optional< NetClock::time_point > closeTime
Definition: Tx.cpp:60
ripple::Transaction::setLedger
void setLedger(LedgerIndex ledger)
Definition: Transaction.h:140
ripple::RPC::Context::ledgerMaster
LedgerMaster & ledgerMaster
Definition: Context.h:45
ripple::LedgerHeader::seq
LedgerIndex seq
Definition: LedgerHeader.h:41
ripple::rpcEXCESSIVE_LGR_RANGE
@ rpcEXCESSIVE_LGR_RANGE
Definition: ErrorCodes.h:135
ripple::TxResult
Definition: Tx.cpp:54
ripple::RelationalDatabase::getLedgerInfoByIndex
virtual std::optional< LedgerInfo > getLedgerInfoByIndex(LedgerIndex ledgerSeq)=0
getLedgerInfoByIndex Returns a ledger by its sequence.
ripple::Family::db
virtual NodeStore::Database & db()=0
ripple::SHAMapLeafNode
Definition: SHAMapLeafNode.h:32
ripple::SHAMapHash
Definition: SHAMapHash.h:32
ripple::RPC::Context::j
const beast::Journal j
Definition: Context.h:41
ripple::LedgerHeader::hash
uint256 hash
Definition: LedgerHeader.h:49
ripple::base_uint< 256 >
ripple::TxArgs::ctid
std::optional< std::pair< uint32_t, uint16_t > > ctid
Definition: Tx.cpp:68
ripple::Ledger::info
LedgerInfo const & info() const override
Returns information about the ledger.
Definition: Ledger.h:152
ripple::rpcSUCCESS
@ rpcSUCCESS
Definition: ErrorCodes.h:44
ripple::Config::reporting
bool reporting() const
Definition: Config.h:350
ripple::Transaction::Locator
Definition: Transaction.h:316
ripple::Application::getLedgerMaster
virtual LedgerMaster & getLedgerMaster()=0
ripple::deserializeTxPlusMeta
std::pair< std::shared_ptr< STTx const >, std::shared_ptr< STObject const > > deserializeTxPlusMeta(SHAMapItem const &item)
Deserialize a SHAMapItem containing STTx + STObject metadata.
Definition: Ledger.cpp:410
ripple::Transaction::getLedger
LedgerIndex getLedger() const
Definition: Transaction.h:101
ripple::TxArgs::ledgerRange
std::optional< std::pair< uint32_t, uint32_t > > ledgerRange
Definition: Tx.cpp:70
ripple::Application::config
virtual Config & config()=0
ripple::Application::getRelationalDatabase
virtual RelationalDatabase & getRelationalDatabase()=0
ripple::rpcTXN_NOT_FOUND
@ rpcTXN_NOT_FOUND
Definition: ErrorCodes.h:80
ripple::RPC::Context::app
Application & app
Definition: Context.h:42
beast::Journal::error
Stream error() const
Definition: Journal.h:332
ripple::Transaction::locate
static Locator locate(uint256 const &id, Application &app)
Definition: Transaction.cpp:134
ripple::LedgerMaster::getLedgerBySeq
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
Definition: LedgerMaster.cpp:1871
ripple::LedgerMaster::txnIdFromIndex
std::optional< uint256 > txnIdFromIndex(uint32_t ledgerSeq, uint32_t txnIndex)
Definition: LedgerMaster.cpp:2426
ripple::SerialIter
Definition: Serializer.h:311
ripple::SerialIter::getVL
Blob getVL()
Definition: Serializer.cpp:508
std::uint32_t
ripple::SerialIter::skip
void skip(int num)
Definition: Serializer.cpp:352
ripple::range
ClosedInterval< T > range(T low, T high)
Create a closed range interval.
Definition: RangeSet.h:54
ripple::TxResult::ctid
std::optional< std::string > ctid
Definition: Tx.cpp:59
ripple::TxResult::validated
bool validated
Definition: Tx.cpp:58
ripple::rpcINTERNAL
@ rpcINTERNAL
Definition: ErrorCodes.h:130
ripple::sfTransactionIndex
const SF_UINT32 sfTransactionIndex
ripple::TxResult::ledgerHash
std::optional< uint256 > ledgerHash
Definition: Tx.cpp:61
ripple::SHAMapTreeNode::makeFromPrefix
static std::shared_ptr< SHAMapTreeNode > makeFromPrefix(Slice rawNode, SHAMapHash const &hash)
Definition: SHAMapTreeNode.cpp:148
ripple::TxArgs
Definition: Tx.cpp:65
ripple::rpcNOT_IMPL
@ rpcNOT_IMPL
Definition: ErrorCodes.h:131
ripple::TxArgs::hash
std::optional< uint256 > hash
Definition: Tx.cpp:67
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::doTxPostgres
std::pair< TxResult, RPC::Status > doTxPostgres(RPC::Context &context, TxArgs const &args)
Definition: Tx.cpp:74
ripple::Config::NETWORK_ID
uint32_t NETWORK_ID
Definition: Config.h:166
ripple::LedgerMaster::getCloseTimeBySeq
std::optional< NetClock::time_point > getCloseTimeBySeq(LedgerIndex ledgerIndex)
Definition: LedgerMaster.cpp:1766
ripple::Transaction::setStatus
void setStatus(TransStatus status, std::uint32_t ledgerSeq)
Definition: Transaction.cpp:62
ripple::SerialIter::getVLDataLength
int getVLDataLength()
Definition: Serializer.cpp:470
ripple::Ledger::open
bool open() const override
Returns true if this reflects an open ledger.
Definition: Ledger.h:146
ripple::doTxHelp
std::pair< TxResult, RPC::Status > doTxHelp(RPC::Context &context, TxArgs args)
Definition: Tx.cpp:192
ripple::TxResult::meta
std::variant< std::shared_ptr< TxMeta >, Blob > meta
Definition: Tx.cpp:57
std::optional< std::string >
beast::Journal::debug
Stream debug() const
Definition: Journal.h:314
ripple::TransactionMaster::fetch
std::variant< std::pair< std::shared_ptr< Transaction >, std::shared_ptr< TxMeta > >, TxSearched > fetch(uint256 const &, error_code_i &ec)
Definition: TransactionMaster.cpp:60
ripple::NodeStore::Database::fetchNodeObject
std::shared_ptr< NodeObject > fetchNodeObject(uint256 const &hash, std::uint32_t ledgerSeq=0, FetchType fetchType=FetchType::synchronous, bool duplicate=false)
Fetch a node object.
Definition: Database.cpp:252
ripple::rpcINVALID_LGR_RANGE
@ rpcINVALID_LGR_RANGE
Definition: ErrorCodes.h:136
ripple::ClosedInterval
boost::icl::closed_interval< T > ClosedInterval
A closed interval over the domain T.
Definition: RangeSet.h:45
ripple::isValidated
static bool isValidated(LedgerMaster &ledgerMaster, std::uint32_t seq, uint256 const &hash)
Definition: Tx.cpp:43
ripple::Transaction::Locator::getLedgerSequence
uint32_t getLedgerSequence()
Definition: Transaction.h:346
ripple::RPC::Context
The context of information needed to call an RPC.
Definition: Context.h:39
ripple::TxResult::searchedAll
TxSearched searchedAll
Definition: Tx.cpp:62
ripple::Transaction::Locator::isFound
bool isFound()
Definition: Transaction.h:327
ripple::TxSearched::some
@ some
ripple::TxResult::txn
Transaction::pointer txn
Definition: Tx.cpp:56
std::variant
ripple::Application::getMasterTransaction
virtual TransactionMaster & getMasterTransaction()=0
ripple::Transaction::Locator::getLedgerRangeSearched
ClosedInterval< uint32_t > const & getLedgerRangeSearched()
Definition: Transaction.h:355
std::chrono::system_clock::now
T now(T... args)