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/NetworkOPs.h>
23 #include <ripple/app/misc/Transaction.h>
24 #include <ripple/basics/ToString.h>
25 #include <ripple/net/RPCErr.h>
26 #include <ripple/protocol/ErrorCodes.h>
27 #include <ripple/protocol/jss.h>
28 #include <ripple/rpc/CTID.h>
29 #include <ripple/rpc/Context.h>
30 #include <ripple/rpc/DeliveredAmount.h>
31 #include <ripple/rpc/GRPCHandlers.h>
32 #include <ripple/rpc/NFTSyntheticSerializer.h>
33 #include <ripple/rpc/impl/RPCHelpers.h>
34 #include <charconv>
35 #include <regex>
36 
37 namespace ripple {
38 
39 static bool
40 isValidated(LedgerMaster& ledgerMaster, std::uint32_t seq, uint256 const& hash)
41 {
42  if (!ledgerMaster.haveLedger(seq))
43  return false;
44 
45  if (seq > ledgerMaster.getValidatedLedger()->info().seq)
46  return false;
47 
48  return ledgerMaster.getHashBySeq(seq) == hash;
49 }
50 
51 struct TxResult
52 {
55  bool validated = false;
58 };
59 
60 struct TxArgs
61 {
64  bool binary = false;
66 };
67 
69 doTxPostgres(RPC::Context& context, TxArgs const& args)
70 {
71  if (!context.app.config().reporting())
72  {
73  assert(false);
74  Throw<std::runtime_error>(
75  "Called doTxPostgres yet not in reporting mode");
76  }
77 
78  TxResult res;
80 
81  if (!args.hash)
82  return {
83  res,
84  {rpcNOT_IMPL,
85  "Use of CTIDs on reporting mode is not currently supported."}};
86 
87  JLOG(context.j.debug()) << "Fetching from postgres";
88  Transaction::Locator locator =
89  Transaction::locate(*(args.hash), context.app);
90 
92  pair;
93  // database returned the nodestore hash. Fetch the txn directly from the
94  // nodestore. Don't traverse the transaction SHAMap
95  if (locator.isFound())
96  {
97  auto start = std::chrono::system_clock::now();
98  // The second argument of fetch is ignored when not using shards
99  if (auto obj = context.app.getNodeFamily().db().fetchNodeObject(
100  locator.getNodestoreHash(), locator.getLedgerSequence()))
101  {
102  auto node = SHAMapTreeNode::makeFromPrefix(
103  makeSlice(obj->getData()),
104  SHAMapHash{locator.getNodestoreHash()});
105  if (!node)
106  {
107  assert(false);
108  return {res, {rpcINTERNAL, "Error making SHAMap node"}};
109  }
110  auto item = (static_cast<SHAMapLeafNode*>(node.get()))->peekItem();
111  if (!item)
112  {
113  assert(false);
114  return {res, {rpcINTERNAL, "Error reading SHAMap node"}};
115  }
116 
117  auto [sttx, meta] = deserializeTxPlusMeta(*item);
118  JLOG(context.j.debug()) << "Successfully fetched from db";
119 
120  if (!sttx || !meta)
121  {
122  assert(false);
123  return {res, {rpcINTERNAL, "Error deserializing SHAMap node"}};
124  }
125  std::string reason;
126  res.txn = std::make_shared<Transaction>(sttx, reason, context.app);
127  res.txn->setLedger(locator.getLedgerSequence());
128  res.txn->setStatus(COMMITTED);
129  if (args.binary)
130  {
131  SerialIter it(item->slice());
132  it.skip(it.getVLDataLength()); // skip transaction
133  Blob blob = it.getVL();
134  res.meta = std::move(blob);
135  }
136  else
137  {
138  res.meta = std::make_shared<TxMeta>(
139  *(args.hash), res.txn->getLedger(), *meta);
140  }
141  res.validated = true;
142  return {res, rpcSUCCESS};
143  }
144  else
145  {
146  JLOG(context.j.error()) << "Failed to fetch from db";
147  assert(false);
148  return {res, {rpcINTERNAL, "Containing SHAMap node not found"}};
149  }
150  auto end = std::chrono::system_clock::now();
151  JLOG(context.j.debug()) << "tx flat fetch time : "
152  << ((end - start).count() / 1000000000.0);
153  }
154  // database did not find the transaction, and returned the ledger range
155  // that was searched
156  else
157  {
158  if (args.ledgerRange)
159  {
160  auto range = locator.getLedgerRangeSearched();
161  auto min = args.ledgerRange->first;
162  auto max = args.ledgerRange->second;
163  if (min >= range.lower() && max <= range.upper())
164  {
166  }
167  else
168  {
170  }
171  }
172  return {res, rpcTXN_NOT_FOUND};
173  }
174  // database didn't return anything. This shouldn't happen
175  assert(false);
176  return {res, {rpcINTERNAL, "unexpected Postgres response"}};
177 }
178 
180 doTxHelp(RPC::Context& context, TxArgs args)
181 {
182  if (context.app.config().reporting())
183  return doTxPostgres(context, args);
184  TxResult result;
185 
187 
188  if (args.ledgerRange)
189  {
190  constexpr uint16_t MAX_RANGE = 1000;
191 
192  if (args.ledgerRange->second < args.ledgerRange->first)
193  return {result, rpcINVALID_LGR_RANGE};
194 
195  if (args.ledgerRange->second - args.ledgerRange->first > MAX_RANGE)
196  return {result, rpcEXCESSIVE_LGR_RANGE};
197 
199  args.ledgerRange->first, args.ledgerRange->second);
200  }
201 
202  auto ec{rpcSUCCESS};
203 
204  using TxPair =
206 
209 
210  if (args.ctid)
211  {
212  args.hash = context.app.getLedgerMaster().txnIdFromIndex(
213  args.ctid->first, args.ctid->second);
214 
215  if (args.hash)
216  range =
217  ClosedInterval<uint32_t>(args.ctid->first, args.ctid->second);
218  }
219 
220  if (!args.hash)
221  return {result, rpcTXN_NOT_FOUND};
222 
223  if (args.ledgerRange)
224  {
225  v = context.app.getMasterTransaction().fetch(*(args.hash), range, ec);
226  }
227  else
228  {
229  v = context.app.getMasterTransaction().fetch(*(args.hash), ec);
230  }
231 
232  if (auto e = std::get_if<TxSearched>(&v))
233  {
234  result.searchedAll = *e;
235  return {result, rpcTXN_NOT_FOUND};
236  }
237 
238  auto [txn, meta] = std::get<TxPair>(v);
239 
240  if (ec == rpcDB_DESERIALIZATION)
241  {
242  return {result, ec};
243  }
244  if (!txn)
245  {
246  return {result, rpcTXN_NOT_FOUND};
247  }
248 
249  // populate transaction data
250  result.txn = txn;
251  if (txn->getLedger() == 0)
252  {
253  return {result, rpcSUCCESS};
254  }
255 
257  context.ledgerMaster.getLedgerBySeq(txn->getLedger());
258 
259  if (ledger && meta)
260  {
261  if (args.binary)
262  {
263  result.meta = meta->getAsObject().getSerializer().getData();
264  }
265  else
266  {
267  result.meta = meta;
268  }
269  result.validated = isValidated(
270  context.ledgerMaster, ledger->info().seq, ledger->info().hash);
271 
272  // compute outgoing CTID
273  uint32_t lgrSeq = ledger->info().seq;
274  uint32_t txnIdx = meta->getAsObject().getFieldU32(sfTransactionIndex);
275  uint32_t netID = context.app.config().NETWORK_ID;
276 
277  if (txnIdx <= 0xFFFFU && netID < 0xFFFFU && lgrSeq < 0x0FFF'FFFFUL)
278  result.ctid =
279  RPC::encodeCTID(lgrSeq, (uint16_t)txnIdx, (uint16_t)netID);
280  }
281 
282  return {result, rpcSUCCESS};
283 }
284 
285 Json::Value
286 populateJsonResponse(
287  std::pair<TxResult, RPC::Status> const& res,
288  TxArgs const& args,
289  RPC::JsonContext const& context)
290 {
291  Json::Value response;
292  RPC::Status const& error = res.second;
293  TxResult const& result = res.first;
294  // handle errors
295  if (error.toErrorCode() != rpcSUCCESS)
296  {
297  if (error.toErrorCode() == rpcTXN_NOT_FOUND &&
298  result.searchedAll != TxSearched::unknown)
299  {
300  response = Json::Value(Json::objectValue);
301  response[jss::searched_all] =
302  (result.searchedAll == TxSearched::all);
303  error.inject(response);
304  }
305  else
306  {
307  error.inject(response);
308  }
309  }
310  // no errors
311  else if (result.txn)
312  {
313  response = result.txn->getJson(JsonOptions::include_date, args.binary);
314 
315  // populate binary metadata
316  if (auto blob = std::get_if<Blob>(&result.meta))
317  {
318  assert(args.binary);
319  response[jss::meta] = strHex(makeSlice(*blob));
320  }
321  // populate meta data
322  else if (auto m = std::get_if<std::shared_ptr<TxMeta>>(&result.meta))
323  {
324  auto& meta = *m;
325  if (meta)
326  {
327  response[jss::meta] = meta->getJson(JsonOptions::none);
328  insertDeliveredAmount(
329  response[jss::meta], context, result.txn, *meta);
330  insertNFTSyntheticInJson(
331  response, context, result.txn->getSTransaction(), *meta);
332  }
333  }
334  response[jss::validated] = result.validated;
335 
336  if (result.ctid)
337  response[jss::ctid] = *(result.ctid);
338  }
339  return response;
340 }
341 
342 Json::Value
343 doTxJson(RPC::JsonContext& context)
344 {
345  if (!context.app.config().useTxTables())
346  return rpcError(rpcNOT_ENABLED);
347 
348  // Deserialize and validate JSON arguments
349 
350  TxArgs args;
351 
352  if (context.params.isMember(jss::transaction) &&
353  context.params.isMember(jss::ctid))
354  // specifying both is ambiguous
355  return rpcError(rpcINVALID_PARAMS);
356 
357  if (context.params.isMember(jss::transaction))
358  {
359  uint256 hash;
360  if (!hash.parseHex(context.params[jss::transaction].asString()))
361  return rpcError(rpcNOT_IMPL);
362  args.hash = hash;
363  }
364  else if (context.params.isMember(jss::ctid))
365  {
366  auto ctid = RPC::decodeCTID(context.params[jss::ctid].asString());
367  if (!ctid)
368  return rpcError(rpcINVALID_PARAMS);
369 
370  auto const [lgr_seq, txn_idx, net_id] = *ctid;
371  if (net_id != context.app.config().NETWORK_ID)
372  {
373  std::stringstream out;
374  out << "Wrong network. You should submit this request to a node "
375  "running on NetworkID: "
376  << net_id;
377  return RPC::make_error(rpcWRONG_NETWORK, out.str());
378  }
379  args.ctid = {lgr_seq, txn_idx};
380  }
381  else
382  return rpcError(rpcINVALID_PARAMS);
383 
384  args.binary = context.params.isMember(jss::binary) &&
385  context.params[jss::binary].asBool();
386 
387  if (context.params.isMember(jss::min_ledger) &&
388  context.params.isMember(jss::max_ledger))
389  {
390  try
391  {
392  args.ledgerRange = std::make_pair(
393  context.params[jss::min_ledger].asUInt(),
394  context.params[jss::max_ledger].asUInt());
395  }
396  catch (...)
397  {
398  // One of the calls to `asUInt ()` failed.
399  return rpcError(rpcINVALID_LGR_RANGE);
400  }
401  }
402 
403  std::pair<TxResult, RPC::Status> res = doTxHelp(context, args);
404  return populateJsonResponse(res, args, context);
405 }
406 
407 } // namespace ripple
ripple::COMMITTED
@ COMMITTED
Definition: Transaction.h:50
ripple::TxArgs::binary
bool binary
Definition: Tx.cpp:64
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::LedgerInfo::hash
uint256 hash
Definition: ReadView.h:91
ripple::TxSearched::all
@ all
ripple::Transaction::Locator::getNodestoreHash
uint256 const & getNodestoreHash()
Definition: Transaction.h:336
ripple::LedgerMaster
Definition: LedgerMaster.h:70
ripple::TxSearched
TxSearched
Definition: Transaction.h:57
charconv
std::vector< unsigned char >
ripple::Transaction::setLedger
void setLedger(LedgerIndex ledger)
Definition: Transaction.h:139
ripple::RPC::Context::ledgerMaster
LedgerMaster & ledgerMaster
Definition: Context.h:45
ripple::rpcEXCESSIVE_LGR_RANGE
@ rpcEXCESSIVE_LGR_RANGE
Definition: ErrorCodes.h:135
ripple::LedgerInfo::seq
LedgerIndex seq
Definition: ReadView.h:83
ripple::TxResult
Definition: Tx.cpp:51
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::base_uint< 256 >
ripple::TxArgs::ctid
std::optional< std::pair< uint32_t, uint16_t > > ctid
Definition: Tx.cpp:63
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:337
ripple::Transaction::Locator
Definition: Transaction.h:315
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:100
ripple::TxArgs::ledgerRange
std::optional< std::pair< uint32_t, uint32_t > > ledgerRange
Definition: Tx.cpp:65
ripple::Application::config
virtual Config & config()=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:333
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:1818
ripple::LedgerMaster::txnIdFromIndex
std::optional< uint256 > txnIdFromIndex(uint32_t ledgerSeq, uint32_t txnIndex)
Definition: LedgerMaster.cpp:2373
ripple::SerialIter
Definition: Serializer.h:310
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:56
ripple::TxResult::validated
bool validated
Definition: Tx.cpp:55
ripple::rpcINTERNAL
@ rpcINTERNAL
Definition: ErrorCodes.h:130
ripple::sfTransactionIndex
const SF_UINT32 sfTransactionIndex
ripple::SHAMapTreeNode::makeFromPrefix
static std::shared_ptr< SHAMapTreeNode > makeFromPrefix(Slice rawNode, SHAMapHash const &hash)
Definition: SHAMapTreeNode.cpp:148
ripple::TxArgs
Definition: Tx.cpp:60
ripple::rpcNOT_IMPL
@ rpcNOT_IMPL
Definition: ErrorCodes.h:131
ripple::TxArgs::hash
std::optional< uint256 > hash
Definition: Tx.cpp:62
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:69
ripple::Config::NETWORK_ID
uint32_t NETWORK_ID
Definition: Config.h:161
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::doTxHelp
std::pair< TxResult, RPC::Status > doTxHelp(RPC::Context &context, TxArgs args)
Definition: Tx.cpp:180
ripple::TxResult::meta
std::variant< std::shared_ptr< TxMeta >, Blob > meta
Definition: Tx.cpp:54
std::optional< std::string >
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
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:40
ripple::Transaction::Locator::getLedgerSequence
uint32_t getLedgerSequence()
Definition: Transaction.h:345
ripple::RPC::Context
The context of information needed to call an RPC.
Definition: Context.h:39
ripple::TxResult::searchedAll
TxSearched searchedAll
Definition: Tx.cpp:57
ripple::Transaction::Locator::isFound
bool isFound()
Definition: Transaction.h:326
ripple::TxSearched::some
@ some
ripple::TxResult::txn
Transaction::pointer txn
Definition: Tx.cpp:53
std::variant
ripple::Application::getMasterTransaction
virtual TransactionMaster & getMasterTransaction()=0
ripple::Transaction::Locator::getLedgerRangeSearched
ClosedInterval< uint32_t > const & getLedgerRangeSearched()
Definition: Transaction.h:354
std::chrono::system_clock::now
T now(T... args)