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