rippled
RelationalDBInterface_postgres.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2021 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/rdb/RelationalDBInterface_postgres.h>
21 #include <ripple/core/Pg.h>
22 #include <ripple/json/json_reader.h>
23 #include <boost/format.hpp>
24 
25 namespace ripple {
26 
29 
33 
36 {
37 #ifdef RIPPLED_REPORTING
38  auto seq = PgQuery(pgPool)("SELECT min_ledger()");
39  if (!seq)
40  {
41  JLOG(j.error()) << "Error querying minimum ledger sequence.";
42  }
43  else if (!seq.isNull())
44  return seq.asInt();
45 #endif
46  return {};
47 }
48 
51 {
52 #ifdef RIPPLED_REPORTING
53  auto seq = PgQuery(pgPool)("SELECT max_ledger()");
54  if (seq && !seq.isNull())
55  return seq.asBigInt();
56 #endif
57  return {};
58 }
59 
62 {
63 #ifdef RIPPLED_REPORTING
64  auto range = PgQuery(pgPool)("SELECT complete_ledgers()");
65  if (range)
66  return range.c_str();
67 #endif
68  return "error";
69 }
70 
73 {
74  using namespace std::chrono_literals;
75 #ifdef RIPPLED_REPORTING
76  auto age = PgQuery(pgPool)("SELECT age()");
77  if (!age || age.isNull())
78  JLOG(j.debug()) << "No ledgers in database";
79  else
80  return std::chrono::seconds{age.asInt()};
81 #endif
82  return weeks{2};
83 }
84 
97  std::shared_ptr<PgPool> const& pgPool,
100  uint256,
101  uint32_t,
102  std::pair<uint32_t, uint32_t>> const& whichLedger,
103  Application& app)
104 {
106 #ifdef RIPPLED_REPORTING
107  auto log = app.journal("Ledger");
108  assert(app.config().reporting());
109  std::stringstream sql;
110  sql << "SELECT ledger_hash, prev_hash, account_set_hash, trans_set_hash, "
111  "total_coins, closing_time, prev_closing_time, close_time_res, "
112  "close_flags, ledger_seq FROM ledgers ";
113 
114  uint32_t expNumResults = 1;
115 
116  if (auto ledgerSeq = std::get_if<uint32_t>(&whichLedger))
117  {
118  sql << "WHERE ledger_seq = " + std::to_string(*ledgerSeq);
119  }
120  else if (auto ledgerHash = std::get_if<uint256>(&whichLedger))
121  {
122  sql << ("WHERE ledger_hash = \'\\x" + strHex(*ledgerHash) + "\'");
123  }
124  else if (
125  auto minAndMax =
127  {
128  expNumResults = minAndMax->second - minAndMax->first;
129 
130  sql
131  << ("WHERE ledger_seq >= " + std::to_string(minAndMax->first) +
132  " AND ledger_seq <= " + std::to_string(minAndMax->second));
133  }
134  else
135  {
136  sql << ("ORDER BY ledger_seq desc LIMIT 1");
137  }
138  sql << ";";
139 
140  JLOG(log.trace()) << __func__ << " : sql = " << sql.str();
141 
142  auto res = PgQuery(pgPool)(sql.str().data());
143  if (!res)
144  {
145  JLOG(log.error()) << __func__ << " : Postgres response is null - sql = "
146  << sql.str();
147  assert(false);
148  return {};
149  }
150  else if (res.status() != PGRES_TUPLES_OK)
151  {
152  JLOG(log.error()) << __func__
153  << " : Postgres response should have been "
154  "PGRES_TUPLES_OK but instead was "
155  << res.status() << " - msg = " << res.msg()
156  << " - sql = " << sql.str();
157  assert(false);
158  return {};
159  }
160 
161  JLOG(log.trace()) << __func__ << " Postgres result msg : " << res.msg();
162 
163  if (res.isNull() || res.ntuples() == 0)
164  {
165  JLOG(log.debug()) << __func__
166  << " : Ledger not found. sql = " << sql.str();
167  return {};
168  }
169  else if (res.ntuples() > 0)
170  {
171  if (res.nfields() != 10)
172  {
173  JLOG(log.error()) << __func__
174  << " : Wrong number of fields in Postgres "
175  "response. Expected 10, but got "
176  << res.nfields() << " . sql = " << sql.str();
177  assert(false);
178  return {};
179  }
180  }
181 
182  for (size_t i = 0; i < res.ntuples(); ++i)
183  {
184  char const* hash = res.c_str(i, 0);
185  char const* prevHash = res.c_str(i, 1);
186  char const* accountHash = res.c_str(i, 2);
187  char const* txHash = res.c_str(i, 3);
188  std::int64_t totalCoins = res.asBigInt(i, 4);
189  std::int64_t closeTime = res.asBigInt(i, 5);
190  std::int64_t parentCloseTime = res.asBigInt(i, 6);
191  std::int64_t closeTimeRes = res.asBigInt(i, 7);
192  std::int64_t closeFlags = res.asBigInt(i, 8);
193  std::int64_t ledgerSeq = res.asBigInt(i, 9);
194 
195  JLOG(log.trace()) << __func__ << " - Postgres response = " << hash
196  << " , " << prevHash << " , " << accountHash << " , "
197  << txHash << " , " << totalCoins << ", " << closeTime
198  << ", " << parentCloseTime << ", " << closeTimeRes
199  << ", " << closeFlags << ", " << ledgerSeq
200  << " - sql = " << sql.str();
201  JLOG(log.debug()) << __func__
202  << " - Successfully fetched ledger with sequence = "
203  << ledgerSeq << " from Postgres";
204 
205  using time_point = NetClock::time_point;
206  using duration = NetClock::duration;
207 
208  LedgerInfo info;
209  if (!info.parentHash.parseHex(prevHash + 2))
210  assert(false);
211  if (!info.txHash.parseHex(txHash + 2))
212  assert(false);
213  if (!info.accountHash.parseHex(accountHash + 2))
214  assert(false);
215  info.drops = totalCoins;
216  info.closeTime = time_point{duration{closeTime}};
217  info.parentCloseTime = time_point{duration{parentCloseTime}};
218  info.closeFlags = closeFlags;
219  info.closeTimeResolution = duration{closeTimeRes};
220  info.seq = ledgerSeq;
221  if (!info.hash.parseHex(hash + 2))
222  assert(false);
223  info.validated = true;
224  infos.push_back(info);
225  }
226 
227 #endif
228  return infos;
229 }
230 
241  std::shared_ptr<PgPool> const& pgPool,
243  Application& app)
244 {
246  std::visit(
247  [&infos, &app, &pgPool](auto&& arg) {
248  infos = loadLedgerInfos(pgPool, arg, app);
249  },
250  whichLedger);
251  assert(infos.size() <= 1);
252  if (!infos.size())
253  return {};
254  return infos[0];
255 }
256 
259 {
260  return loadLedgerHelper(pgPool, {}, app);
261 }
262 
265  std::shared_ptr<PgPool> const& pgPool,
266  std::uint32_t ledgerIndex,
267  Application& app)
268 {
269  return loadLedgerHelper(pgPool, uint32_t{ledgerIndex}, app);
270 }
271 
274  std::shared_ptr<PgPool> const& pgPool,
275  uint256 const& ledgerHash,
276  Application& app)
277 {
278  return loadLedgerHelper(pgPool, uint256{ledgerHash}, app);
279 }
280 
281 uint256
283  std::shared_ptr<PgPool> const& pgPool,
284  std::uint32_t ledgerIndex,
285  Application& app)
286 {
287  auto infos = loadLedgerInfos(pgPool, ledgerIndex, app);
288  assert(infos.size() <= 1);
289  if (infos.size())
290  return infos[0].hash;
291  return {};
292 }
293 
294 bool
296  std::shared_ptr<PgPool> const& pgPool,
297  std::uint32_t ledgerIndex,
298  uint256& ledgerHash,
299  uint256& parentHash,
300  Application& app)
301 {
302  auto infos = loadLedgerInfos(pgPool, ledgerIndex, app);
303  assert(infos.size() <= 1);
304  if (infos.size())
305  {
306  ledgerHash = infos[0].hash;
307  parentHash = infos[0].parentHash;
308  return true;
309  }
310  return false;
311 }
312 
315  std::shared_ptr<PgPool> const& pgPool,
316  std::uint32_t minSeq,
317  std::uint32_t maxSeq,
318  Application& app)
319 {
321  auto infos = loadLedgerInfos(pgPool, std::make_pair(minSeq, maxSeq), app);
322  for (auto& info : infos)
323  {
324  ret[info.seq] = {info.hash, info.parentHash};
325  }
326  return ret;
327 }
328 
331  std::shared_ptr<PgPool> const& pgPool,
332  LedgerIndex seq,
333  Application& app)
334 {
335  std::vector<uint256> nodestoreHashes;
336 
337 #ifdef RIPPLED_REPORTING
338  auto log = app.journal("Ledger");
339 
340  std::string query =
341  "SELECT nodestore_hash"
342  " FROM transactions "
343  " WHERE ledger_seq = " +
344  std::to_string(seq);
345  auto res = PgQuery(pgPool)(query.c_str());
346 
347  if (!res)
348  {
349  JLOG(log.error()) << __func__
350  << " : Postgres response is null - query = " << query;
351  assert(false);
352  return {};
353  }
354  else if (res.status() != PGRES_TUPLES_OK)
355  {
356  JLOG(log.error()) << __func__
357  << " : Postgres response should have been "
358  "PGRES_TUPLES_OK but instead was "
359  << res.status() << " - msg = " << res.msg()
360  << " - query = " << query;
361  assert(false);
362  return {};
363  }
364 
365  JLOG(log.trace()) << __func__ << " Postgres result msg : " << res.msg();
366 
367  if (res.isNull() || res.ntuples() == 0)
368  {
369  JLOG(log.debug()) << __func__
370  << " : Ledger not found. query = " << query;
371  return {};
372  }
373  else if (res.ntuples() > 0)
374  {
375  if (res.nfields() != 1)
376  {
377  JLOG(log.error()) << __func__
378  << " : Wrong number of fields in Postgres "
379  "response. Expected 1, but got "
380  << res.nfields() << " . query = " << query;
381  assert(false);
382  return {};
383  }
384  }
385 
386  JLOG(log.trace()) << __func__ << " : result = " << res.c_str()
387  << " : query = " << query;
388  for (size_t i = 0; i < res.ntuples(); ++i)
389  {
390  char const* nodestoreHash = res.c_str(i, 0);
391  uint256 hash;
392  if (!hash.parseHex(nodestoreHash + 2))
393  assert(false);
394 
395  nodestoreHashes.push_back(hash);
396  }
397 #endif
398 
399  return nodestoreHashes;
400 }
401 
402 #ifdef RIPPLED_REPORTING
403 enum class DataFormat { binary, expanded };
406  Application& app,
407  std::vector<uint256>& nodestoreHashes,
408  std::vector<uint32_t>& ledgerSequences,
409  DataFormat format)
410 {
412  if (format == DataFormat::binary)
413  ret = TxnsDataBinary();
414  else
415  ret = TxnsData();
416 
417  std::vector<
419  txns = flatFetchTransactions(app, nodestoreHashes);
420  for (size_t i = 0; i < txns.size(); ++i)
421  {
422  auto& [txn, meta] = txns[i];
423  if (format == DataFormat::binary)
424  {
425  auto& transactions = std::get<TxnsDataBinary>(ret);
426  Serializer txnSer = txn->getSerializer();
427  Serializer metaSer = meta->getSerializer();
428  // SerialIter it(item->slice());
429  Blob txnBlob = txnSer.getData();
430  Blob metaBlob = metaSer.getData();
431  transactions.push_back(
432  std::make_tuple(txnBlob, metaBlob, ledgerSequences[i]));
433  }
434  else
435  {
436  auto& transactions = std::get<TxnsData>(ret);
437  std::string reason;
438  auto txnRet = std::make_shared<Transaction>(txn, reason, app);
439  txnRet->setLedger(ledgerSequences[i]);
440  txnRet->setStatus(COMMITTED);
441  auto txMeta = std::make_shared<TxMeta>(
442  txnRet->getID(), ledgerSequences[i], *meta);
443  transactions.push_back(std::make_pair(txnRet, txMeta));
444  }
445  }
446  return ret;
447 }
448 
450 processAccountTxStoredProcedureResult(
451  AccountTxArgs const& args,
452  Json::Value& result,
453  Application& app,
454  beast::Journal j)
455 {
456  AccountTxResult ret;
457  ret.limit = args.limit;
458 
459  try
460  {
461  if (result.isMember("transactions"))
462  {
463  std::vector<uint256> nodestoreHashes;
464  std::vector<uint32_t> ledgerSequences;
465  for (auto& t : result["transactions"])
466  {
467  if (t.isMember("ledger_seq") && t.isMember("nodestore_hash"))
468  {
469  uint32_t ledgerSequence = t["ledger_seq"].asUInt();
470  std::string nodestoreHashHex =
471  t["nodestore_hash"].asString();
472  nodestoreHashHex.erase(0, 2);
473  uint256 nodestoreHash;
474  if (!nodestoreHash.parseHex(nodestoreHashHex))
475  assert(false);
476 
477  if (nodestoreHash.isNonZero())
478  {
479  ledgerSequences.push_back(ledgerSequence);
480  nodestoreHashes.push_back(nodestoreHash);
481  }
482  else
483  {
484  assert(false);
485  return {ret, {rpcINTERNAL, "nodestoreHash is zero"}};
486  }
487  }
488  else
489  {
490  assert(false);
491  return {ret, {rpcINTERNAL, "missing postgres fields"}};
492  }
493  }
494 
495  assert(nodestoreHashes.size() == ledgerSequences.size());
496  ret.transactions = flatFetchTransactions(
497  app,
498  nodestoreHashes,
499  ledgerSequences,
500  args.binary ? DataFormat::binary : DataFormat::expanded);
501 
502  JLOG(j.trace()) << __func__ << " : processed db results";
503 
504  if (result.isMember("marker"))
505  {
506  auto& marker = result["marker"];
507  assert(marker.isMember("ledger"));
508  assert(marker.isMember("seq"));
509  ret.marker = {
510  marker["ledger"].asUInt(), marker["seq"].asUInt()};
511  }
512  assert(result.isMember("ledger_index_min"));
513  assert(result.isMember("ledger_index_max"));
514  ret.ledgerRange = {
515  result["ledger_index_min"].asUInt(),
516  result["ledger_index_max"].asUInt()};
517  return {ret, rpcSUCCESS};
518  }
519  else if (result.isMember("error"))
520  {
521  JLOG(j.debug())
522  << __func__ << " : error = " << result["error"].asString();
523  return {
524  ret,
525  RPC::Status{rpcINVALID_PARAMS, result["error"].asString()}};
526  }
527  else
528  {
529  return {ret, {rpcINTERNAL, "unexpected Postgres response"}};
530  }
531  }
532  catch (std::exception& e)
533  {
534  JLOG(j.debug()) << __func__ << " : "
535  << "Caught exception : " << e.what();
536  return {ret, {rpcINTERNAL, e.what()}};
537  }
538 }
539 #endif
540 
543  std::shared_ptr<PgPool> const& pgPool,
544  AccountTxArgs const& args,
545  Application& app,
546  beast::Journal j)
547 {
548 #ifdef RIPPLED_REPORTING
549  pg_params dbParams;
550 
551  char const*& command = dbParams.first;
552  std::vector<std::optional<std::string>>& values = dbParams.second;
553  command =
554  "SELECT account_tx($1::bytea, $2::bool, "
555  "$3::bigint, $4::bigint, $5::bigint, $6::bytea, "
556  "$7::bigint, $8::bool, $9::bigint, $10::bigint)";
557  values.resize(10);
558  values[0] = "\\x" + strHex(args.account);
559  values[1] = args.forward ? "true" : "false";
560 
561  static std::uint32_t const page_length(200);
562  if (args.limit == 0 || args.limit > page_length)
563  values[2] = std::to_string(page_length);
564  else
565  values[2] = std::to_string(args.limit);
566 
567  if (args.ledger)
568  {
569  if (auto range = std::get_if<LedgerRange>(&args.ledger.value()))
570  {
571  values[3] = std::to_string(range->min);
572  values[4] = std::to_string(range->max);
573  }
574  else if (auto hash = std::get_if<LedgerHash>(&args.ledger.value()))
575  {
576  values[5] = ("\\x" + strHex(*hash));
577  }
578  else if (
579  auto sequence = std::get_if<LedgerSequence>(&args.ledger.value()))
580  {
581  values[6] = std::to_string(*sequence);
582  }
583  else if (std::get_if<LedgerShortcut>(&args.ledger.value()))
584  {
585  // current, closed and validated are all treated as validated
586  values[7] = "true";
587  }
588  else
589  {
590  JLOG(j.error()) << "doAccountTxStoredProcedure - "
591  << "Error parsing ledger args";
592  return {};
593  }
594  }
595 
596  if (args.marker)
597  {
598  values[8] = std::to_string(args.marker->ledgerSeq);
599  values[9] = std::to_string(args.marker->txnSeq);
600  }
601  for (size_t i = 0; i < values.size(); ++i)
602  {
603  JLOG(j.trace()) << "value " << std::to_string(i) << " = "
604  << (values[i] ? values[i].value() : "null");
605  }
606 
607  auto res = PgQuery(pgPool)(dbParams);
608  if (!res)
609  {
610  JLOG(j.error()) << __func__
611  << " : Postgres response is null - account = "
612  << strHex(args.account);
613  assert(false);
614  return {{}, {rpcINTERNAL, "Postgres error"}};
615  }
616  else if (res.status() != PGRES_TUPLES_OK)
617  {
618  JLOG(j.error()) << __func__
619  << " : Postgres response should have been "
620  "PGRES_TUPLES_OK but instead was "
621  << res.status() << " - msg = " << res.msg()
622  << " - account = " << strHex(args.account);
623  assert(false);
624  return {{}, {rpcINTERNAL, "Postgres error"}};
625  }
626 
627  JLOG(j.trace()) << __func__ << " Postgres result msg : " << res.msg();
628  if (res.isNull() || res.ntuples() == 0)
629  {
630  JLOG(j.debug()) << __func__
631  << " : No data returned from Postgres : account = "
632  << strHex(args.account);
633 
634  assert(false);
635  return {{}, {rpcINTERNAL, "Postgres error"}};
636  }
637 
638  char const* resultStr = res.c_str();
639  JLOG(j.trace()) << __func__ << " : "
640  << "postgres result = " << resultStr
641  << " : account = " << strHex(args.account);
642 
643  Json::Value v;
644  Json::Reader reader;
645  bool success = reader.parse(resultStr, resultStr + strlen(resultStr), v);
646  if (success)
647  {
648  return processAccountTxStoredProcedureResult(args, v, app, j);
649  }
650 #endif
651  // This shouldn't happen. Postgres should return a parseable error
652  assert(false);
653  return {{}, {rpcINTERNAL, "Failed to deserialize Postgres result"}};
654 }
655 
656 Transaction::Locator
658  std::shared_ptr<PgPool> const& pgPool,
659  uint256 const& id,
660  Application& app)
661 {
662 #ifdef RIPPLED_REPORTING
663  auto baseCmd = boost::format(R"(SELECT tx('%s');)");
664 
665  std::string txHash = "\\x" + strHex(id);
666  std::string sql = boost::str(baseCmd % txHash);
667 
668  auto res = PgQuery(pgPool)(sql.data());
669 
670  if (!res)
671  {
672  JLOG(app.journal("Transaction").error())
673  << __func__
674  << " : Postgres response is null - tx ID = " << strHex(id);
675  assert(false);
676  return {};
677  }
678  else if (res.status() != PGRES_TUPLES_OK)
679  {
680  JLOG(app.journal("Transaction").error())
681  << __func__
682  << " : Postgres response should have been "
683  "PGRES_TUPLES_OK but instead was "
684  << res.status() << " - msg = " << res.msg()
685  << " - tx ID = " << strHex(id);
686  assert(false);
687  return {};
688  }
689 
690  JLOG(app.journal("Transaction").trace())
691  << __func__ << " Postgres result msg : " << res.msg();
692  if (res.isNull() || res.ntuples() == 0)
693  {
694  JLOG(app.journal("Transaction").debug())
695  << __func__
696  << " : No data returned from Postgres : tx ID = " << strHex(id);
697  // This shouldn't happen
698  assert(false);
699  return {};
700  }
701 
702  char const* resultStr = res.c_str();
703  JLOG(app.journal("Transaction").debug())
704  << "postgres result = " << resultStr;
705 
706  Json::Value v;
707  Json::Reader reader;
708  bool success = reader.parse(resultStr, resultStr + strlen(resultStr), v);
709  if (success)
710  {
711  if (v.isMember("nodestore_hash") && v.isMember("ledger_seq"))
712  {
713  uint256 nodestoreHash;
714  if (!nodestoreHash.parseHex(
715  v["nodestore_hash"].asString().substr(2)))
716  assert(false);
717  uint32_t ledgerSeq = v["ledger_seq"].asUInt();
718  if (nodestoreHash.isNonZero())
719  return {std::make_pair(nodestoreHash, ledgerSeq)};
720  }
721  if (v.isMember("min_seq") && v.isMember("max_seq"))
722  {
723  return {ClosedInterval<uint32_t>(
724  v["min_seq"].asUInt(), v["max_seq"].asUInt())};
725  }
726  }
727 #endif
728  // Shouldn' happen. Postgres should return the ledger range searched if
729  // the transaction was not found
730  assert(false);
731  Throw<std::runtime_error>(
732  "Transaction::Locate - Invalid Postgres response");
733  return {};
734 }
735 
736 #ifdef RIPPLED_REPORTING
737 static bool
738 writeToLedgersDB(LedgerInfo const& info, PgQuery& pgQuery, beast::Journal& j)
739 {
740  JLOG(j.debug()) << __func__;
741  auto cmd = boost::format(
742  R"(INSERT INTO ledgers
743  VALUES (%u,'\x%s', '\x%s',%u,%u,%u,%u,%u,'\x%s','\x%s'))");
744 
745  auto ledgerInsert = boost::str(
746  cmd % info.seq % strHex(info.hash) % strHex(info.parentHash) %
747  info.drops.drops() % info.closeTime.time_since_epoch().count() %
748  info.parentCloseTime.time_since_epoch().count() %
749  info.closeTimeResolution.count() % info.closeFlags %
750  strHex(info.accountHash) % strHex(info.txHash));
751  JLOG(j.trace()) << __func__ << " : "
752  << " : "
753  << "query string = " << ledgerInsert;
754 
755  auto res = pgQuery(ledgerInsert.data());
756 
757  return res;
758 }
759 #endif
760 
761 bool
763  std::shared_ptr<PgPool> const& pgPool,
764  LedgerInfo const& info,
765  std::vector<AccountTransactionsData> const& accountTxData,
766  beast::Journal& j)
767 {
768 #ifdef RIPPLED_REPORTING
769  JLOG(j.debug()) << __func__ << " : "
770  << "Beginning write to Postgres";
771 
772  try
773  {
774  // Create a PgQuery object to run multiple commands over the same
775  // connection in a single transaction block.
776  PgQuery pg(pgPool);
777  auto res = pg("BEGIN");
778  if (!res || res.status() != PGRES_COMMAND_OK)
779  {
780  std::stringstream msg;
781  msg << "bulkWriteToTable : Postgres insert error: " << res.msg();
782  Throw<std::runtime_error>(msg.str());
783  }
784 
785  // Writing to the ledgers db fails if the ledger already exists in the
786  // db. In this situation, the ETL process has detected there is another
787  // writer, and falls back to only publishing
788  if (!writeToLedgersDB(info, pg, j))
789  {
790  JLOG(j.warn()) << __func__ << " : "
791  << "Failed to write to ledgers database.";
792  return false;
793  }
794 
795  std::stringstream transactionsCopyBuffer;
796  std::stringstream accountTransactionsCopyBuffer;
797  for (auto const& data : accountTxData)
798  {
799  std::string txHash = strHex(data.txHash);
800  std::string nodestoreHash = strHex(data.nodestoreHash);
801  auto idx = data.transactionIndex;
802  auto ledgerSeq = data.ledgerSequence;
803 
804  transactionsCopyBuffer << std::to_string(ledgerSeq) << '\t'
805  << std::to_string(idx) << '\t' << "\\\\x"
806  << txHash << '\t' << "\\\\x" << nodestoreHash
807  << '\n';
808 
809  for (auto const& a : data.accounts)
810  {
811  std::string acct = strHex(a);
812  accountTransactionsCopyBuffer
813  << "\\\\x" << acct << '\t' << std::to_string(ledgerSeq)
814  << '\t' << std::to_string(idx) << '\n';
815  }
816  }
817 
818  pg.bulkInsert("transactions", transactionsCopyBuffer.str());
819  pg.bulkInsert(
820  "account_transactions", accountTransactionsCopyBuffer.str());
821 
822  res = pg("COMMIT");
823  if (!res || res.status() != PGRES_COMMAND_OK)
824  {
825  std::stringstream msg;
826  msg << "bulkWriteToTable : Postgres insert error: " << res.msg();
827  assert(false);
828  Throw<std::runtime_error>(msg.str());
829  }
830 
831  JLOG(j.info()) << __func__ << " : "
832  << "Successfully wrote to Postgres";
833  return true;
834  }
835  catch (std::exception& e)
836  {
837  JLOG(j.error()) << __func__ << "Caught exception writing to Postgres : "
838  << e.what();
839  assert(false);
840  return false;
841  }
842 #else
843  return false;
844 #endif
845 }
846 
849  std::shared_ptr<PgPool> const& pgPool,
850  LedgerIndex startIndex,
851  Application& app,
852  beast::Journal j)
853 {
855 
856 #ifdef RIPPLED_REPORTING
857  if (!app.config().reporting())
858  {
859  assert(false);
860  Throw<std::runtime_error>(
861  "called getTxHistory but not in reporting mode");
862  }
863 
864  std::string sql = boost::str(
865  boost::format("SELECT nodestore_hash, ledger_seq "
866  " FROM transactions"
867  " ORDER BY ledger_seq DESC LIMIT 20 "
868  "OFFSET %u;") %
869  startIndex);
870 
871  auto res = PgQuery(pgPool)(sql.data());
872 
873  if (!res)
874  {
875  JLOG(j.error()) << __func__
876  << " : Postgres response is null - sql = " << sql;
877  assert(false);
878  return {};
879  }
880  else if (res.status() != PGRES_TUPLES_OK)
881  {
882  JLOG(j.error()) << __func__
883  << " : Postgres response should have been "
884  "PGRES_TUPLES_OK but instead was "
885  << res.status() << " - msg = " << res.msg()
886  << " - sql = " << sql;
887  assert(false);
888  return {};
889  }
890 
891  JLOG(j.trace()) << __func__ << " Postgres result msg : " << res.msg();
892 
893  if (res.isNull() || res.ntuples() == 0)
894  {
895  JLOG(j.debug()) << __func__ << " : Empty postgres response";
896  assert(false);
897  return {};
898  }
899  else if (res.ntuples() > 0)
900  {
901  if (res.nfields() != 2)
902  {
903  JLOG(j.error()) << __func__
904  << " : Wrong number of fields in Postgres "
905  "response. Expected 1, but got "
906  << res.nfields() << " . sql = " << sql;
907  assert(false);
908  return {};
909  }
910  }
911 
912  JLOG(j.trace()) << __func__ << " : Postgres result = " << res.c_str();
913 
914  std::vector<uint256> nodestoreHashes;
915  std::vector<uint32_t> ledgerSequences;
916  for (size_t i = 0; i < res.ntuples(); ++i)
917  {
918  uint256 hash;
919  if (!hash.parseHex(res.c_str(i, 0) + 2))
920  assert(false);
921  nodestoreHashes.push_back(hash);
922  ledgerSequences.push_back(res.asBigInt(i, 1));
923  }
924 
925  auto txns = flatFetchTransactions(app, nodestoreHashes);
926  for (size_t i = 0; i < txns.size(); ++i)
927  {
928  auto const& [sttx, meta] = txns[i];
929  assert(sttx);
930 
931  std::string reason;
932  auto txn = std::make_shared<Transaction>(sttx, reason, app);
933  txn->setLedger(ledgerSequences[i]);
934  txn->setStatus(COMMITTED);
935  ret.push_back(txn);
936  }
937 
938 #endif
939  return ret;
940 }
941 
942 } // namespace ripple
ripple::COMMITTED
@ COMMITTED
Definition: Transaction.h:50
ripple::loadLedgerInfos
static std::vector< LedgerInfo > loadLedgerInfos(std::shared_ptr< PgPool > const &pgPool, std::variant< std::monostate, uint256, uint32_t, std::pair< uint32_t, uint32_t >> const &whichLedger, Application &app)
loadLedgerInfos Load the ledger info for the specified ledger/s from the database
Definition: RelationalDBInterface_postgres.cpp:96
ripple::RelationalDBInterface::LedgerSequence
uint32_t LedgerSequence
Definition: RelationalDBInterface.h:90
ripple::getLedgerInfoByIndex
std::optional< LedgerInfo > getLedgerInfoByIndex(soci::session &session, LedgerIndex ledgerSeq, beast::Journal j)
getLedgerInfoByIndex Returns ledger by its sequence.
Definition: RelationalDBInterface_nodes.cpp:455
ripple::Application
Definition: Application.h:115
std::vector::resize
T resize(T... args)
std::make_tuple
T make_tuple(T... args)
ripple::Blob
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition: Blob.h:30
ripple::RelationalDBInterface::AccountTxArgs
Definition: RelationalDBInterface.h:96
ripple::getAccountTx
std::pair< AccountTxResult, RPC::Status > getAccountTx(std::shared_ptr< PgPool > const &pgPool, AccountTxArgs const &args, Application &app, beast::Journal j)
getAccountTx Get last account transactions specifies by passed argumenrs structure.
Definition: RelationalDBInterface_postgres.cpp:542
std::string
STL class.
std::shared_ptr< PgPool >
ripple::getTxHistory
std::pair< std::vector< std::shared_ptr< Transaction > >, int > getTxHistory(soci::session &session, Application &app, LedgerIndex startIndex, int quantity, bool count)
getTxHistory Returns given number of most recent transactions starting from given number of entry.
Definition: RelationalDBInterface_nodes.cpp:615
ripple::LedgerInfo::parentHash
uint256 parentHash
Definition: ReadView.h:103
ripple::getHashByIndex
uint256 getHashByIndex(soci::session &session, LedgerIndex ledgerIndex)
getHashByIndex Returns hash of ledger with given sequence.
Definition: RelationalDBInterface_nodes.cpp:509
ripple::rpcINVALID_PARAMS
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
std::exception
STL class.
ripple::base_uint::isNonZero
bool isNonZero() const
Definition: base_uint.h:516
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
std::pair< uint32_t, uint32_t >
ripple::RPC::LedgerShortcut
LedgerShortcut
Definition: RPCHelpers.h:109
ripple::LedgerInfo::hash
uint256 hash
Definition: ReadView.h:100
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::RelationalDBInterface::AccountTxArgs::marker
std::optional< AccountTxMarker > marker
Definition: RelationalDBInterface.h:103
std::chrono::seconds
ripple::getLedgerInfoByHash
std::optional< LedgerInfo > getLedgerInfoByHash(soci::session &session, uint256 const &ledgerHash, beast::Journal j)
getLedgerInfoByHash Returns info of ledger with given hash.
Definition: RelationalDBInterface_nodes.cpp:498
std::stringstream
STL class.
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
std::monostate
ripple::AccountTxResult
RelationalDBInterface::AccountTxResult AccountTxResult
Definition: RelationalDBInterface_postgres.h:37
ripple::locateTransaction
Transaction::Locator locateTransaction(std::shared_ptr< PgPool > const &pgPool, uint256 const &id, Application &app)
locateTransaction Returns information used to locate a transaction.
Definition: RelationalDBInterface_postgres.cpp:657
ripple::RelationalDBInterface::AccountTxArgs::ledger
std::optional< LedgerSpecifier > ledger
Definition: RelationalDBInterface.h:99
ripple::LedgerInfo::seq
LedgerIndex seq
Definition: ReadView.h:92
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::LedgerInfo::txHash
uint256 txHash
Definition: ReadView.h:101
ripple::LedgerSequence
RelationalDBInterface::LedgerSequence LedgerSequence
Definition: RelationalDBInterface_postgres.cpp:31
ripple::getCompleteLedgers
std::string getCompleteLedgers(std::shared_ptr< PgPool > const &pgPool)
getCompleteLedgers Returns string which contains list of completed ledgers
Definition: RelationalDBInterface_postgres.cpp:61
ripple::uint256
base_uint< 256 > uint256
Definition: base_uint.h:529
std::vector::push_back
T push_back(T... args)
ripple::LedgerInfo::closeTime
NetClock::time_point closeTime
Definition: ReadView.h:123
ripple::RelationalDBInterface::LedgerHash
uint256 LedgerHash
Definition: RelationalDBInterface.h:91
ripple::base_uint< 256 >
ripple::writeLedgerAndTransactions
bool writeLedgerAndTransactions(std::shared_ptr< PgPool > const &pgPool, LedgerInfo const &info, std::vector< AccountTransactionsData > const &accountTxData, beast::Journal &j)
writeLedgerAndTransactions Write new ledger and transaction data to Postgres.
Definition: RelationalDBInterface_postgres.cpp:762
ripple::getValidatedLedgerAge
std::chrono::seconds getValidatedLedgerAge(std::shared_ptr< PgPool > const &pgPool, beast::Journal j)
getValidatedLedgerAge Returns age of last validated ledger
Definition: RelationalDBInterface_postgres.cpp:72
ripple::rpcSUCCESS
@ rpcSUCCESS
Definition: ErrorCodes.h:44
ripple::Config::reporting
bool reporting() const
Definition: Config.h:308
ripple::RelationalDBInterface::LedgerShortcut
RPC::LedgerShortcut LedgerShortcut
Definition: RelationalDBInterface.h:92
ripple::Application::config
virtual Config & config()=0
std::string::c_str
T c_str(T... args)
ripple::LedgerInfo::closeFlags
int closeFlags
Definition: ReadView.h:114
std::to_string
T to_string(T... args)
beast::Journal::error
Stream error() const
Definition: Journal.h:333
beast::Journal::info
Stream info() const
Definition: Journal.h:321
std::string::erase
T erase(T... args)
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::int64_t
std::map
STL class.
ripple::range
ClosedInterval< T > range(T low, T high)
Create a closed range interval.
Definition: RangeSet.h:53
ripple::LedgerInfo::drops
XRPAmount drops
Definition: ReadView.h:105
ripple::RelationalDBInterface::AccountTxArgs::limit
uint32_t limit
Definition: RelationalDBInterface.h:102
ripple::rpcINTERNAL
@ rpcINTERNAL
Definition: ErrorCodes.h:130
std::string::substr
T substr(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Application::journal
virtual beast::Journal journal(std::string const &name)=0
ripple::RelationalDBInterface::AccountTxArgs::account
AccountID account
Definition: RelationalDBInterface.h:98
ripple::TxnsData
RelationalDBInterface::AccountTxs TxnsData
Definition: RelationalDBInterface_postgres.cpp:27
ripple::RelationalDBInterface::MetaTxsList
std::vector< txnMetaLedgerType > MetaTxsList
Definition: RelationalDBInterface.h:88
ripple::getHashesByIndex
std::optional< LedgerHashPair > getHashesByIndex(soci::session &session, LedgerIndex ledgerIndex, beast::Journal j)
getHashesByIndex Returns hash of the ledger and hash of parent ledger for the ledger of given sequenc...
Definition: RelationalDBInterface_nodes.cpp:539
ripple::LedgerInfo::closeTimeResolution
NetClock::duration closeTimeResolution
Definition: ReadView.h:117
Json::Value::asUInt
UInt asUInt() const
Definition: json_value.cpp:545
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
std::get_if
T get_if(T... args)
std::visit
T visit(T... args)
std::optional
std::stringstream::str
T str(T... args)
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
std::make_pair
T make_pair(T... args)
ripple::LedgerInfo
Information about the notional ledger backing the view.
Definition: ReadView.h:84
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:45
ripple::ClosedInterval
boost::icl::closed_interval< T > ClosedInterval
A closed interval over the domain T.
Definition: RangeSet.h:44
ripple::NetClock::duration
std::chrono::duration< rep, period > duration
Definition: chrono.h:55
ripple::base_uint::parseHex
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition: base_uint.h:475
ripple::RelationalDBInterface::AccountTxArgs::forward
bool forward
Definition: RelationalDBInterface.h:101
ripple::getMinLedgerSeq
std::optional< LedgerIndex > getMinLedgerSeq(soci::session &session, TableType type)
getMinLedgerSeq Returns minimum ledger sequence in given table.
Definition: RelationalDBInterface_nodes.cpp:121
ripple::LedgerInfo::validated
bool validated
Definition: ReadView.h:110
ripple::loadLedgerHelper
std::shared_ptr< Ledger > loadLedgerHelper(LedgerInfo const &info, Application &app, bool acquire)
Definition: Ledger.cpp:1017
ripple::NetClock::time_point
std::chrono::time_point< NetClock > time_point
Definition: chrono.h:56
std::string::data
T data(T... args)
ripple::getNewestLedgerInfo
std::optional< LedgerInfo > getNewestLedgerInfo(soci::session &session, beast::Journal j)
getNewestLedgerInfo Returns info of newest saved ledger.
Definition: RelationalDBInterface_nodes.cpp:466
ripple::TxnsDataBinary
RelationalDBInterface::MetaTxsList TxnsDataBinary
Definition: RelationalDBInterface_postgres.cpp:28
ripple::LedgerInfo::accountHash
uint256 accountHash
Definition: ReadView.h:102
ripple::getMaxLedgerSeq
std::optional< LedgerIndex > getMaxLedgerSeq(soci::session &session, TableType type)
getMaxLedgerSeq Returns maximum ledger sequence in given table.
Definition: RelationalDBInterface_nodes.cpp:131
std::exception::what
T what(T... args)
ripple::AccountTxArgs
RelationalDBInterface::AccountTxArgs AccountTxArgs
Definition: RelationalDBInterface_postgres.h:36
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::getTxHashes
std::vector< uint256 > getTxHashes(std::shared_ptr< PgPool > const &pgPool, LedgerIndex seq, Application &app)
getTxHashes Returns vector of tx hashes by given ledger sequence
Definition: RelationalDBInterface_postgres.cpp:330
std::variant
ripple::RelationalDBInterface::AccountTxs
std::vector< AccountTx > AccountTxs
Definition: RelationalDBInterface.h:86
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
ripple::LedgerInfo::parentCloseTime
NetClock::time_point parentCloseTime
Definition: ReadView.h:93
ripple::flatFetchTransactions
std::vector< std::pair< std::shared_ptr< STTx const >, std::shared_ptr< STObject const > > > flatFetchTransactions(Application &app, std::vector< uint256 > &nodestoreHashes)
Definition: Ledger.cpp:1089