rippled
PostgresDatabase.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2020 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/AcceptedLedger.h>
21 #include <ripple/app/ledger/LedgerMaster.h>
22 #include <ripple/app/ledger/LedgerToJson.h>
23 #include <ripple/app/ledger/TransactionMaster.h>
24 #include <ripple/app/main/Application.h>
25 #include <ripple/app/misc/Manifest.h>
26 #include <ripple/app/misc/impl/AccountTxPaging.h>
27 #include <ripple/app/rdb/backend/PostgresDatabase.h>
28 #include <ripple/app/rdb/backend/detail/Node.h>
29 #include <ripple/basics/BasicConfig.h>
30 #include <ripple/basics/StringUtilities.h>
31 #include <ripple/core/DatabaseCon.h>
32 #include <ripple/core/Pg.h>
33 #include <ripple/core/SociDB.h>
34 #include <ripple/json/json_reader.h>
35 #include <ripple/json/to_string.h>
36 #include <ripple/nodestore/DatabaseShard.h>
37 #include <boost/algorithm/string.hpp>
38 #include <boost/range/adaptor/transformed.hpp>
39 #include <soci/sqlite3/soci-sqlite3.h>
40 
41 namespace ripple {
42 
43 class PgPool;
44 
48 
50 {
51 public:
53  Application& app,
54  Config const& config,
55  JobQueue& jobQueue)
56  : app_(app)
57  , j_(app_.journal("PgPool"))
58  , pgPool_(
59 #ifdef RIPPLED_REPORTING
60  make_PgPool(config.section("ledger_tx_tables"), j_)
61 #endif
62  )
63  {
64  assert(config.reporting());
65 #ifdef RIPPLED_REPORTING
66  if (config.reporting() && !config.reportingReadOnly()) // use pg
67  {
68  initSchema(pgPool_);
69  }
70 #endif
71  }
72 
73  void
74  stop() override
75  {
76 #ifdef RIPPLED_REPORTING
77  pgPool_->stop();
78 #endif
79  }
80 
81  void
82  sweep() override;
83 
85  getMinLedgerSeq() override;
86 
88  getMaxLedgerSeq() override;
89 
91  getCompleteLedgers() override;
92 
94  getValidatedLedgerAge() override;
95 
96  bool
98  LedgerInfo const& info,
99  std::vector<AccountTransactionsData> const& accountTxData) override;
100 
102  getLedgerInfoByIndex(LedgerIndex ledgerSeq) override;
103 
105  getNewestLedgerInfo() override;
106 
108  getLedgerInfoByHash(uint256 const& ledgerHash) override;
109 
110  uint256
111  getHashByIndex(LedgerIndex ledgerIndex) override;
112 
114  getHashesByIndex(LedgerIndex ledgerIndex) override;
115 
117  getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) override;
118 
120  getTxHashes(LedgerIndex seq) override;
121 
123  getTxHistory(LedgerIndex startIndex) override;
124 
126  getAccountTx(AccountTxArgs const& args) override;
127 
129  locateTransaction(uint256 const& id) override;
130 
131  bool
132  ledgerDbHasSpace(Config const& config) override;
133 
134  bool
135  transactionDbHasSpace(Config const& config) override;
136 
137  bool
138  isCaughtUp(std::string& reason) override;
139 
140 private:
144 
145  bool
146  dbHasSpace(Config const& config);
147 };
148 
161  std::shared_ptr<PgPool> const& pgPool,
162  std::variant<
164  uint256,
165  uint32_t,
166  std::pair<uint32_t, uint32_t>> const& whichLedger,
167  Application& app)
168 {
170 #ifdef RIPPLED_REPORTING
171  auto log = app.journal("Ledger");
172  assert(app.config().reporting());
173  std::stringstream sql;
174  sql << "SELECT ledger_hash, prev_hash, account_set_hash, trans_set_hash, "
175  "total_coins, closing_time, prev_closing_time, close_time_res, "
176  "close_flags, ledger_seq FROM ledgers ";
177 
178  if (auto ledgerSeq = std::get_if<uint32_t>(&whichLedger))
179  {
180  sql << "WHERE ledger_seq = " + std::to_string(*ledgerSeq);
181  }
182  else if (auto ledgerHash = std::get_if<uint256>(&whichLedger))
183  {
184  sql << ("WHERE ledger_hash = \'\\x" + strHex(*ledgerHash) + "\'");
185  }
186  else if (
187  auto minAndMax =
189  {
190  sql
191  << ("WHERE ledger_seq >= " + std::to_string(minAndMax->first) +
192  " AND ledger_seq <= " + std::to_string(minAndMax->second));
193  }
194  else
195  {
196  sql << ("ORDER BY ledger_seq desc LIMIT 1");
197  }
198  sql << ";";
199 
200  JLOG(log.trace()) << __func__ << " : sql = " << sql.str();
201 
202  auto res = PgQuery(pgPool)(sql.str().data());
203  if (!res)
204  {
205  JLOG(log.error()) << __func__ << " : Postgres response is null - sql = "
206  << sql.str();
207  assert(false);
208  return {};
209  }
210  else if (res.status() != PGRES_TUPLES_OK)
211  {
212  JLOG(log.error()) << __func__
213  << " : Postgres response should have been "
214  "PGRES_TUPLES_OK but instead was "
215  << res.status() << " - msg = " << res.msg()
216  << " - sql = " << sql.str();
217  assert(false);
218  return {};
219  }
220 
221  JLOG(log.trace()) << __func__ << " Postgres result msg : " << res.msg();
222 
223  if (res.isNull() || res.ntuples() == 0)
224  {
225  JLOG(log.debug()) << __func__
226  << " : Ledger not found. sql = " << sql.str();
227  return {};
228  }
229  else if (res.ntuples() > 0)
230  {
231  if (res.nfields() != 10)
232  {
233  JLOG(log.error()) << __func__
234  << " : Wrong number of fields in Postgres "
235  "response. Expected 10, but got "
236  << res.nfields() << " . sql = " << sql.str();
237  assert(false);
238  return {};
239  }
240  }
241 
242  for (size_t i = 0; i < res.ntuples(); ++i)
243  {
244  char const* hash = res.c_str(i, 0);
245  char const* prevHash = res.c_str(i, 1);
246  char const* accountHash = res.c_str(i, 2);
247  char const* txHash = res.c_str(i, 3);
248  std::int64_t totalCoins = res.asBigInt(i, 4);
249  std::int64_t closeTime = res.asBigInt(i, 5);
250  std::int64_t parentCloseTime = res.asBigInt(i, 6);
251  std::int64_t closeTimeRes = res.asBigInt(i, 7);
252  std::int64_t closeFlags = res.asBigInt(i, 8);
253  std::int64_t ledgerSeq = res.asBigInt(i, 9);
254 
255  JLOG(log.trace()) << __func__ << " - Postgres response = " << hash
256  << " , " << prevHash << " , " << accountHash << " , "
257  << txHash << " , " << totalCoins << ", " << closeTime
258  << ", " << parentCloseTime << ", " << closeTimeRes
259  << ", " << closeFlags << ", " << ledgerSeq
260  << " - sql = " << sql.str();
261  JLOG(log.debug()) << __func__
262  << " - Successfully fetched ledger with sequence = "
263  << ledgerSeq << " from Postgres";
264 
265  using time_point = NetClock::time_point;
266  using duration = NetClock::duration;
267 
268  LedgerInfo info;
269  if (!info.parentHash.parseHex(prevHash + 2))
270  assert(false);
271  if (!info.txHash.parseHex(txHash + 2))
272  assert(false);
273  if (!info.accountHash.parseHex(accountHash + 2))
274  assert(false);
275  info.drops = totalCoins;
276  info.closeTime = time_point{duration{closeTime}};
277  info.parentCloseTime = time_point{duration{parentCloseTime}};
278  info.closeFlags = closeFlags;
279  info.closeTimeResolution = duration{closeTimeRes};
280  info.seq = ledgerSeq;
281  if (!info.hash.parseHex(hash + 2))
282  assert(false);
283  info.validated = true;
284  infos.push_back(info);
285  }
286 
287 #endif
288  return infos;
289 }
290 
301  std::shared_ptr<PgPool> const& pgPool,
303  Application& app)
304 {
306  std::visit(
307  [&infos, &app, &pgPool](auto&& arg) {
308  infos = loadLedgerInfos(pgPool, arg, app);
309  },
310  whichLedger);
311  assert(infos.size() <= 1);
312  if (!infos.size())
313  return {};
314  return infos[0];
315 }
316 
317 #ifdef RIPPLED_REPORTING
318 static bool
319 writeToLedgersDB(LedgerInfo const& info, PgQuery& pgQuery, beast::Journal& j)
320 {
321  JLOG(j.debug()) << __func__;
322  auto cmd = boost::format(
323  R"(INSERT INTO ledgers
324  VALUES (%u,'\x%s', '\x%s',%u,%u,%u,%u,%u,'\x%s','\x%s'))");
325 
326  auto ledgerInsert = boost::str(
327  cmd % info.seq % strHex(info.hash) % strHex(info.parentHash) %
328  info.drops.drops() % info.closeTime.time_since_epoch().count() %
329  info.parentCloseTime.time_since_epoch().count() %
330  info.closeTimeResolution.count() % info.closeFlags %
331  strHex(info.accountHash) % strHex(info.txHash));
332  JLOG(j.trace()) << __func__ << " : "
333  << " : "
334  << "query string = " << ledgerInsert;
335 
336  auto res = pgQuery(ledgerInsert.data());
337 
338  return res;
339 }
340 
341 enum class DataFormat { binary, expanded };
344  Application& app,
345  std::vector<uint256>& nodestoreHashes,
346  std::vector<uint32_t>& ledgerSequences,
347  DataFormat format)
348 {
350  if (format == DataFormat::binary)
351  ret = TxnsDataBinary();
352  else
353  ret = TxnsData();
354 
355  std::vector<
357  txns = flatFetchTransactions(app, nodestoreHashes);
358  for (size_t i = 0; i < txns.size(); ++i)
359  {
360  auto& [txn, meta] = txns[i];
361  if (format == DataFormat::binary)
362  {
363  auto& transactions = std::get<TxnsDataBinary>(ret);
364  Serializer txnSer = txn->getSerializer();
365  Serializer metaSer = meta->getSerializer();
366  // SerialIter it(item->slice());
367  Blob txnBlob = txnSer.getData();
368  Blob metaBlob = metaSer.getData();
369  transactions.push_back(
370  std::make_tuple(txnBlob, metaBlob, ledgerSequences[i]));
371  }
372  else
373  {
374  auto& transactions = std::get<TxnsData>(ret);
375  std::string reason;
376  auto txnRet = std::make_shared<Transaction>(txn, reason, app);
377  txnRet->setLedger(ledgerSequences[i]);
378  txnRet->setStatus(COMMITTED);
379  auto txMeta = std::make_shared<TxMeta>(
380  txnRet->getID(), ledgerSequences[i], *meta);
381  transactions.push_back(std::make_pair(txnRet, txMeta));
382  }
383  }
384  return ret;
385 }
386 
388 processAccountTxStoredProcedureResult(
389  RelationalDatabase::AccountTxArgs const& args,
390  Json::Value& result,
391  Application& app,
392  beast::Journal j)
393 {
394  AccountTxResult ret;
395  ret.limit = args.limit;
396 
397  try
398  {
399  if (result.isMember("transactions"))
400  {
401  std::vector<uint256> nodestoreHashes;
402  std::vector<uint32_t> ledgerSequences;
403  for (auto& t : result["transactions"])
404  {
405  if (t.isMember("ledger_seq") && t.isMember("nodestore_hash"))
406  {
407  uint32_t ledgerSequence = t["ledger_seq"].asUInt();
408  std::string nodestoreHashHex =
409  t["nodestore_hash"].asString();
410  nodestoreHashHex.erase(0, 2);
411  uint256 nodestoreHash;
412  if (!nodestoreHash.parseHex(nodestoreHashHex))
413  assert(false);
414 
415  if (nodestoreHash.isNonZero())
416  {
417  ledgerSequences.push_back(ledgerSequence);
418  nodestoreHashes.push_back(nodestoreHash);
419  }
420  else
421  {
422  assert(false);
423  return {ret, {rpcINTERNAL, "nodestoreHash is zero"}};
424  }
425  }
426  else
427  {
428  assert(false);
429  return {ret, {rpcINTERNAL, "missing postgres fields"}};
430  }
431  }
432 
433  assert(nodestoreHashes.size() == ledgerSequences.size());
434  ret.transactions = flatFetchTransactions(
435  app,
436  nodestoreHashes,
437  ledgerSequences,
438  args.binary ? DataFormat::binary : DataFormat::expanded);
439 
440  JLOG(j.trace()) << __func__ << " : processed db results";
441 
442  if (result.isMember("marker"))
443  {
444  auto& marker = result["marker"];
445  assert(marker.isMember("ledger"));
446  assert(marker.isMember("seq"));
447  ret.marker = {
448  marker["ledger"].asUInt(), marker["seq"].asUInt()};
449  }
450  assert(result.isMember("ledger_index_min"));
451  assert(result.isMember("ledger_index_max"));
452  ret.ledgerRange = {
453  result["ledger_index_min"].asUInt(),
454  result["ledger_index_max"].asUInt()};
455  return {ret, rpcSUCCESS};
456  }
457  else if (result.isMember("error"))
458  {
459  JLOG(j.debug())
460  << __func__ << " : error = " << result["error"].asString();
461  return {
462  ret,
463  RPC::Status{rpcINVALID_PARAMS, result["error"].asString()}};
464  }
465  else
466  {
467  return {ret, {rpcINTERNAL, "unexpected Postgres response"}};
468  }
469  }
470  catch (std::exception& e)
471  {
472  JLOG(j.debug()) << __func__ << " : "
473  << "Caught exception : " << e.what();
474  return {ret, {rpcINTERNAL, e.what()}};
475  }
476 }
477 #endif
478 
479 void
481 {
482 #ifdef RIPPLED_REPORTING
483  pgPool_->idleSweeper();
484 #endif
485 }
486 
489 {
490 #ifdef RIPPLED_REPORTING
491  auto seq = PgQuery(pgPool_)("SELECT min_ledger()");
492  if (!seq)
493  {
494  JLOG(j_.error()) << "Error querying minimum ledger sequence.";
495  }
496  else if (!seq.isNull())
497  return seq.asInt();
498 #endif
499  return {};
500 }
501 
504 {
505 #ifdef RIPPLED_REPORTING
506  auto seq = PgQuery(pgPool_)("SELECT max_ledger()");
507  if (seq && !seq.isNull())
508  return seq.asBigInt();
509 #endif
510  return {};
511 }
512 
515 {
516 #ifdef RIPPLED_REPORTING
517  auto range = PgQuery(pgPool_)("SELECT complete_ledgers()");
518  if (range)
519  return range.c_str();
520 #endif
521  return "error";
522 }
523 
526 {
527  using namespace std::chrono_literals;
528 #ifdef RIPPLED_REPORTING
529  auto age = PgQuery(pgPool_)("SELECT age()");
530  if (!age || age.isNull())
531  JLOG(j_.debug()) << "No ledgers in database";
532  else
533  return std::chrono::seconds{age.asInt()};
534 #endif
535  return weeks{2};
536 }
537 
538 bool
540  LedgerInfo const& info,
541  std::vector<AccountTransactionsData> const& accountTxData)
542 {
543 #ifdef RIPPLED_REPORTING
544  JLOG(j_.debug()) << __func__ << " : "
545  << "Beginning write to Postgres";
546 
547  try
548  {
549  // Create a PgQuery object to run multiple commands over the same
550  // connection in a single transaction block.
551  PgQuery pg(pgPool_);
552  auto res = pg("BEGIN");
553  if (!res || res.status() != PGRES_COMMAND_OK)
554  {
555  std::stringstream msg;
556  msg << "bulkWriteToTable : Postgres insert error: " << res.msg();
557  Throw<std::runtime_error>(msg.str());
558  }
559 
560  // Writing to the ledgers db fails if the ledger already exists in the
561  // db. In this situation, the ETL process has detected there is another
562  // writer, and falls back to only publishing
563  if (!writeToLedgersDB(info, pg, j_))
564  {
565  JLOG(j_.warn()) << __func__ << " : "
566  << "Failed to write to ledgers database.";
567  return false;
568  }
569 
570  std::stringstream transactionsCopyBuffer;
571  std::stringstream accountTransactionsCopyBuffer;
572  for (auto const& data : accountTxData)
573  {
574  std::string txHash = strHex(data.txHash);
575  std::string nodestoreHash = strHex(data.nodestoreHash);
576  auto idx = data.transactionIndex;
577  auto ledgerSeq = data.ledgerSequence;
578 
579  transactionsCopyBuffer << std::to_string(ledgerSeq) << '\t'
580  << std::to_string(idx) << '\t' << "\\\\x"
581  << txHash << '\t' << "\\\\x" << nodestoreHash
582  << '\n';
583 
584  for (auto const& a : data.accounts)
585  {
586  std::string acct = strHex(a);
587  accountTransactionsCopyBuffer
588  << "\\\\x" << acct << '\t' << std::to_string(ledgerSeq)
589  << '\t' << std::to_string(idx) << '\n';
590  }
591  }
592 
593  pg.bulkInsert("transactions", transactionsCopyBuffer.str());
594  pg.bulkInsert(
595  "account_transactions", accountTransactionsCopyBuffer.str());
596 
597  res = pg("COMMIT");
598  if (!res || res.status() != PGRES_COMMAND_OK)
599  {
600  std::stringstream msg;
601  msg << "bulkWriteToTable : Postgres insert error: " << res.msg();
602  assert(false);
603  Throw<std::runtime_error>(msg.str());
604  }
605 
606  JLOG(j_.info()) << __func__ << " : "
607  << "Successfully wrote to Postgres";
608  return true;
609  }
610  catch (std::exception& e)
611  {
612  JLOG(j_.error()) << __func__
613  << "Caught exception writing to Postgres : "
614  << e.what();
615  assert(false);
616  return false;
617  }
618 #else
619  return false;
620 #endif
621 }
622 
625 {
626  return loadLedgerHelper(pgPool_, ledgerSeq, app_);
627 }
628 
631 {
632  return loadLedgerHelper(pgPool_, {}, app_);
633 }
634 
637 {
638  return loadLedgerHelper(pgPool_, ledgerHash, app_);
639 }
640 
641 uint256
643 {
644  auto infos = loadLedgerInfos(pgPool_, ledgerIndex, app_);
645  assert(infos.size() <= 1);
646  if (infos.size())
647  return infos[0].hash;
648  return {};
649 }
650 
653 {
654  LedgerHashPair p;
655  auto infos = loadLedgerInfos(pgPool_, ledgerIndex, app_);
656  assert(infos.size() <= 1);
657  if (infos.size())
658  {
659  p.ledgerHash = infos[0].hash;
660  p.parentHash = infos[0].parentHash;
661  return p;
662  }
663  return {};
664 }
665 
668 {
670  auto infos = loadLedgerInfos(pgPool_, std::make_pair(minSeq, maxSeq), app_);
671  for (auto& info : infos)
672  {
673  ret[info.seq] = {info.hash, info.parentHash};
674  }
675  return ret;
676 }
677 
680 {
681  std::vector<uint256> nodestoreHashes;
682 
683 #ifdef RIPPLED_REPORTING
684  auto log = app_.journal("Ledger");
685 
686  std::string query =
687  "SELECT nodestore_hash"
688  " FROM transactions "
689  " WHERE ledger_seq = " +
690  std::to_string(seq);
691  auto res = PgQuery(pgPool_)(query.c_str());
692 
693  if (!res)
694  {
695  JLOG(log.error()) << __func__
696  << " : Postgres response is null - query = " << query;
697  assert(false);
698  return {};
699  }
700  else if (res.status() != PGRES_TUPLES_OK)
701  {
702  JLOG(log.error()) << __func__
703  << " : Postgres response should have been "
704  "PGRES_TUPLES_OK but instead was "
705  << res.status() << " - msg = " << res.msg()
706  << " - query = " << query;
707  assert(false);
708  return {};
709  }
710 
711  JLOG(log.trace()) << __func__ << " Postgres result msg : " << res.msg();
712 
713  if (res.isNull() || res.ntuples() == 0)
714  {
715  JLOG(log.debug()) << __func__
716  << " : Ledger not found. query = " << query;
717  return {};
718  }
719  else if (res.ntuples() > 0)
720  {
721  if (res.nfields() != 1)
722  {
723  JLOG(log.error()) << __func__
724  << " : Wrong number of fields in Postgres "
725  "response. Expected 1, but got "
726  << res.nfields() << " . query = " << query;
727  assert(false);
728  return {};
729  }
730  }
731 
732  JLOG(log.trace()) << __func__ << " : result = " << res.c_str()
733  << " : query = " << query;
734  for (size_t i = 0; i < res.ntuples(); ++i)
735  {
736  char const* nodestoreHash = res.c_str(i, 0);
737  uint256 hash;
738  if (!hash.parseHex(nodestoreHash + 2))
739  assert(false);
740 
741  nodestoreHashes.push_back(hash);
742  }
743 #endif
744 
745  return nodestoreHashes;
746 }
747 
750 {
752 
753 #ifdef RIPPLED_REPORTING
754  if (!app_.config().reporting())
755  {
756  assert(false);
757  Throw<std::runtime_error>(
758  "called getTxHistory but not in reporting mode");
759  }
760 
761  std::string sql = boost::str(
762  boost::format("SELECT nodestore_hash, ledger_seq "
763  " FROM transactions"
764  " ORDER BY ledger_seq DESC LIMIT 20 "
765  "OFFSET %u;") %
766  startIndex);
767 
768  auto res = PgQuery(pgPool_)(sql.data());
769 
770  if (!res)
771  {
772  JLOG(j_.error()) << __func__
773  << " : Postgres response is null - sql = " << sql;
774  assert(false);
775  return {};
776  }
777  else if (res.status() != PGRES_TUPLES_OK)
778  {
779  JLOG(j_.error()) << __func__
780  << " : Postgres response should have been "
781  "PGRES_TUPLES_OK but instead was "
782  << res.status() << " - msg = " << res.msg()
783  << " - sql = " << sql;
784  assert(false);
785  return {};
786  }
787 
788  JLOG(j_.trace()) << __func__ << " Postgres result msg : " << res.msg();
789 
790  if (res.isNull() || res.ntuples() == 0)
791  {
792  JLOG(j_.debug()) << __func__ << " : Empty postgres response";
793  assert(false);
794  return {};
795  }
796  else if (res.ntuples() > 0)
797  {
798  if (res.nfields() != 2)
799  {
800  JLOG(j_.error()) << __func__
801  << " : Wrong number of fields in Postgres "
802  "response. Expected 1, but got "
803  << res.nfields() << " . sql = " << sql;
804  assert(false);
805  return {};
806  }
807  }
808 
809  JLOG(j_.trace()) << __func__ << " : Postgres result = " << res.c_str();
810 
811  std::vector<uint256> nodestoreHashes;
812  std::vector<uint32_t> ledgerSequences;
813  for (size_t i = 0; i < res.ntuples(); ++i)
814  {
815  uint256 hash;
816  if (!hash.parseHex(res.c_str(i, 0) + 2))
817  assert(false);
818  nodestoreHashes.push_back(hash);
819  ledgerSequences.push_back(res.asBigInt(i, 1));
820  }
821 
822  auto txns = flatFetchTransactions(app_, nodestoreHashes);
823  for (size_t i = 0; i < txns.size(); ++i)
824  {
825  auto const& [sttx, meta] = txns[i];
826  assert(sttx);
827 
828  std::string reason;
829  auto txn = std::make_shared<Transaction>(sttx, reason, app_);
830  txn->setLedger(ledgerSequences[i]);
831  txn->setStatus(COMMITTED);
832  ret.push_back(txn);
833  }
834 
835 #endif
836  return ret;
837 }
838 
841 {
842 #ifdef RIPPLED_REPORTING
843  pg_params dbParams;
844 
845  char const*& command = dbParams.first;
846  std::vector<std::optional<std::string>>& values = dbParams.second;
847  command =
848  "SELECT account_tx($1::bytea, $2::bool, "
849  "$3::bigint, $4::bigint, $5::bigint, $6::bytea, "
850  "$7::bigint, $8::bool, $9::bigint, $10::bigint)";
851  values.resize(10);
852  values[0] = "\\x" + strHex(args.account);
853  values[1] = args.forward ? "true" : "false";
854 
855  static std::uint32_t const page_length(200);
856  if (args.limit == 0 || args.limit > page_length)
857  values[2] = std::to_string(page_length);
858  else
859  values[2] = std::to_string(args.limit);
860 
861  if (args.ledger)
862  {
863  if (auto range = std::get_if<LedgerRange>(&args.ledger.value()))
864  {
865  values[3] = std::to_string(range->min);
866  values[4] = std::to_string(range->max);
867  }
868  else if (auto hash = std::get_if<LedgerHash>(&args.ledger.value()))
869  {
870  values[5] = ("\\x" + strHex(*hash));
871  }
872  else if (
873  auto sequence = std::get_if<LedgerSequence>(&args.ledger.value()))
874  {
875  values[6] = std::to_string(*sequence);
876  }
877  else if (std::get_if<LedgerShortcut>(&args.ledger.value()))
878  {
879  // current, closed and validated are all treated as validated
880  values[7] = "true";
881  }
882  else
883  {
884  JLOG(j_.error()) << "doAccountTxStoredProcedure - "
885  << "Error parsing ledger args";
886  return {};
887  }
888  }
889 
890  if (args.marker)
891  {
892  values[8] = std::to_string(args.marker->ledgerSeq);
893  values[9] = std::to_string(args.marker->txnSeq);
894  }
895  for (size_t i = 0; i < values.size(); ++i)
896  {
897  JLOG(j_.trace()) << "value " << std::to_string(i) << " = "
898  << (values[i] ? values[i].value() : "null");
899  }
900 
901  auto res = PgQuery(pgPool_)(dbParams);
902  if (!res)
903  {
904  JLOG(j_.error()) << __func__
905  << " : Postgres response is null - account = "
906  << strHex(args.account);
907  assert(false);
908  return {{}, {rpcINTERNAL, "Postgres error"}};
909  }
910  else if (res.status() != PGRES_TUPLES_OK)
911  {
912  JLOG(j_.error()) << __func__
913  << " : Postgres response should have been "
914  "PGRES_TUPLES_OK but instead was "
915  << res.status() << " - msg = " << res.msg()
916  << " - account = " << strHex(args.account);
917  assert(false);
918  return {{}, {rpcINTERNAL, "Postgres error"}};
919  }
920 
921  JLOG(j_.trace()) << __func__ << " Postgres result msg : " << res.msg();
922  if (res.isNull() || res.ntuples() == 0)
923  {
924  JLOG(j_.debug()) << __func__
925  << " : No data returned from Postgres : account = "
926  << strHex(args.account);
927 
928  assert(false);
929  return {{}, {rpcINTERNAL, "Postgres error"}};
930  }
931 
932  char const* resultStr = res.c_str();
933  JLOG(j_.trace()) << __func__ << " : "
934  << "postgres result = " << resultStr
935  << " : account = " << strHex(args.account);
936 
937  Json::Value v;
938  Json::Reader reader;
939  bool success = reader.parse(resultStr, resultStr + strlen(resultStr), v);
940  if (success)
941  {
942  return processAccountTxStoredProcedureResult(args, v, app_, j_);
943  }
944 #endif
945  // This shouldn't happen. Postgres should return a parseable error
946  assert(false);
947  return {{}, {rpcINTERNAL, "Failed to deserialize Postgres result"}};
948 }
949 
952 {
953 #ifdef RIPPLED_REPORTING
954  auto baseCmd = boost::format(R"(SELECT tx('%s');)");
955 
956  std::string txHash = "\\x" + strHex(id);
957  std::string sql = boost::str(baseCmd % txHash);
958 
959  auto res = PgQuery(pgPool_)(sql.data());
960 
961  if (!res)
962  {
963  JLOG(app_.journal("Transaction").error())
964  << __func__
965  << " : Postgres response is null - tx ID = " << strHex(id);
966  assert(false);
967  return {};
968  }
969  else if (res.status() != PGRES_TUPLES_OK)
970  {
971  JLOG(app_.journal("Transaction").error())
972  << __func__
973  << " : Postgres response should have been "
974  "PGRES_TUPLES_OK but instead was "
975  << res.status() << " - msg = " << res.msg()
976  << " - tx ID = " << strHex(id);
977  assert(false);
978  return {};
979  }
980 
981  JLOG(app_.journal("Transaction").trace())
982  << __func__ << " Postgres result msg : " << res.msg();
983  if (res.isNull() || res.ntuples() == 0)
984  {
985  JLOG(app_.journal("Transaction").debug())
986  << __func__
987  << " : No data returned from Postgres : tx ID = " << strHex(id);
988  // This shouldn't happen
989  assert(false);
990  return {};
991  }
992 
993  char const* resultStr = res.c_str();
994  JLOG(app_.journal("Transaction").debug())
995  << "postgres result = " << resultStr;
996 
997  Json::Value v;
998  Json::Reader reader;
999  bool success = reader.parse(resultStr, resultStr + strlen(resultStr), v);
1000  if (success)
1001  {
1002  if (v.isMember("nodestore_hash") && v.isMember("ledger_seq"))
1003  {
1004  uint256 nodestoreHash;
1005  if (!nodestoreHash.parseHex(
1006  v["nodestore_hash"].asString().substr(2)))
1007  assert(false);
1008  uint32_t ledgerSeq = v["ledger_seq"].asUInt();
1009  if (nodestoreHash.isNonZero())
1010  return {std::make_pair(nodestoreHash, ledgerSeq)};
1011  }
1012  if (v.isMember("min_seq") && v.isMember("max_seq"))
1013  {
1014  return {ClosedInterval<uint32_t>(
1015  v["min_seq"].asUInt(), v["max_seq"].asUInt())};
1016  }
1017  }
1018 #endif
1019  // Shouldn' happen. Postgres should return the ledger range searched if
1020  // the transaction was not found
1021  assert(false);
1022  Throw<std::runtime_error>(
1023  "Transaction::Locate - Invalid Postgres response");
1024  return {};
1025 }
1026 
1027 bool
1029 {
1030  /* Postgres server could be running on a different machine. */
1031 
1032  return true;
1033 }
1034 
1035 bool
1037 {
1038  return dbHasSpace(config);
1039 }
1040 
1041 bool
1043 {
1044  return dbHasSpace(config);
1045 }
1046 
1048 getPostgresDatabase(Application& app, Config const& config, JobQueue& jobQueue)
1049 {
1050  return std::make_unique<PostgresDatabaseImp>(app, config, jobQueue);
1051 }
1052 
1053 bool
1055 {
1056 #ifdef RIPPLED_REPORTING
1057  using namespace std::chrono_literals;
1058  auto age = PgQuery(pgPool_)("SELECT age()");
1059  if (!age || age.isNull())
1060  {
1061  reason = "No ledgers in database";
1062  return false;
1063  }
1064  if (std::chrono::seconds{age.asInt()} > 3min)
1065  {
1066  reason = "No recently-published ledger";
1067  return false;
1068  }
1069 #endif
1070  return true;
1071 }
1072 
1073 } // namespace ripple
ripple::COMMITTED
@ COMMITTED
Definition: Transaction.h:51
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 Loads the ledger info for the specified ledger/s from the database
Definition: PostgresDatabase.cpp:160
ripple::Application
Definition: Application.h:116
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::PostgresDatabaseImp
Definition: PostgresDatabase.cpp:49
std::string
STL class.
std::shared_ptr< PgPool >
ripple::rpcINVALID_PARAMS
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
ripple::PostgresDatabaseImp::locateTransaction
Transaction::Locator locateTransaction(uint256 const &id) override
locateTransaction Returns information used to locate a transaction.
Definition: PostgresDatabase.cpp:951
ripple::LedgerHeader::closeFlags
int closeFlags
Definition: LedgerHeader.h:63
std::exception
STL class.
ripple::base_uint::isNonZero
bool isNonZero() const
Definition: base_uint.h:537
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:308
ripple::RelationalDatabase::AccountTxArgs
Definition: RelationalDatabase.h:96
std::pair
ripple::PostgresDatabaseImp::transactionDbHasSpace
bool transactionDbHasSpace(Config const &config) override
transactionDbHasSpace Checks if the transaction database has available space.
Definition: PostgresDatabase.cpp:1042
ripple::PostgresDatabaseImp::stop
void stop() override
Definition: PostgresDatabase.cpp:74
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::LedgerHeader::parentHash
uint256 parentHash
Definition: LedgerHeader.h:52
std::chrono::seconds
ripple::RelationalDatabase::AccountTxs
std::vector< AccountTx > AccountTxs
Definition: RelationalDatabase.h:86
ripple::LedgerHeader::seq
LedgerIndex seq
Definition: LedgerHeader.h:41
ripple::LedgerInfo
LedgerHeader LedgerInfo
Definition: LedgerHeader.h:79
std::stringstream
STL class.
beast::Journal::warn
Stream warn() const
Definition: Journal.h:326
ripple::PostgresDatabaseImp::getValidatedLedgerAge
std::chrono::seconds getValidatedLedgerAge() override
getValidatedLedgerAge Returns the age of the last validated ledger.
Definition: PostgresDatabase.cpp:525
std::monostate
ripple::LedgerHeader::accountHash
uint256 accountHash
Definition: LedgerHeader.h:51
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::RelationalDatabase::AccountTxArgs::account
AccountID account
Definition: RelationalDatabase.h:98
ripple::PostgresDatabaseImp::getNewestLedgerInfo
std::optional< LedgerInfo > getNewestLedgerInfo() override
getNewestLedgerInfo Returns the info of the newest saved ledger.
Definition: PostgresDatabase.cpp:630
ripple::LedgerHeader::txHash
uint256 txHash
Definition: LedgerHeader.h:50
ripple::RelationalDatabase::AccountTxResult
Definition: RelationalDatabase.h:106
ripple::PostgresDatabaseImp::PostgresDatabaseImp
PostgresDatabaseImp(Application &app, Config const &config, JobQueue &jobQueue)
Definition: PostgresDatabase.cpp:52
ripple::PostgresDatabaseImp::getMinLedgerSeq
std::optional< LedgerIndex > getMinLedgerSeq() override
getMinLedgerSeq Returns the minimum ledger sequence in the Ledgers table.
Definition: PostgresDatabase.cpp:488
ripple::uint256
base_uint< 256 > uint256
Definition: base_uint.h:550
std::vector::push_back
T push_back(T... args)
ripple::LedgerHeader::hash
uint256 hash
Definition: LedgerHeader.h:49
ripple::base_uint< 256 >
ripple::LedgerHeader::parentCloseTime
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
ripple::rpcSUCCESS
@ rpcSUCCESS
Definition: ErrorCodes.h:44
ripple::Config::reporting
bool reporting() const
Definition: Config.h:350
ripple::PostgresDatabaseImp::getHashesByIndex
std::optional< LedgerHashPair > getHashesByIndex(LedgerIndex ledgerIndex) override
getHashesByIndex Returns the hashes of the ledger and its parent as specified by the ledgerIndex.
Definition: PostgresDatabase.cpp:652
ripple::PostgresDatabaseImp::getAccountTx
std::pair< AccountTxResult, RPC::Status > getAccountTx(AccountTxArgs const &args) override
getAccountTx Get the last account transactions specified by the AccountTxArgs struct.
Definition: PostgresDatabase.cpp:840
ripple::LedgerHashPair::ledgerHash
uint256 ledgerHash
Definition: RelationalDatabase.h:38
ripple::PostgresDatabaseImp::app_
Application & app_
Definition: PostgresDatabase.cpp:141
ripple::Transaction::Locator
Definition: Transaction.h:316
ripple::RelationalDatabase::MetaTxsList
std::vector< txnMetaLedgerType > MetaTxsList
Definition: RelationalDatabase.h:88
ripple::Config
Definition: Config.h:92
ripple::LedgerHashPair
Definition: RelationalDatabase.h:36
ripple::Application::config
virtual Config & config()=0
ripple::RelationalDatabase::AccountTxArgs::marker
std::optional< AccountTxMarker > marker
Definition: RelationalDatabase.h:103
ripple::RelationalDatabase::AccountTxArgs::limit
uint32_t limit
Definition: RelationalDatabase.h:102
std::string::c_str
T c_str(T... args)
ripple::PostgresDatabaseImp::dbHasSpace
bool dbHasSpace(Config const &config)
Definition: PostgresDatabase.cpp:1028
std::to_string
T to_string(T... args)
beast::Journal::error
Stream error() const
Definition: Journal.h:332
beast::Journal::info
Stream info() const
Definition: Journal.h:320
ripple::PostgresDatabaseImp::getHashByIndex
uint256 getHashByIndex(LedgerIndex ledgerIndex) override
getHashByIndex Returns the hash of the ledger with the given sequence.
Definition: PostgresDatabase.cpp:642
std::string::erase
T erase(T... args)
ripple::getPostgresDatabase
std::unique_ptr< RelationalDatabase > getPostgresDatabase(Application &app, Config const &config, JobQueue &jobQueue)
Definition: PostgresDatabase.cpp:1048
ripple::TxnsDataBinary
RelationalDatabase::MetaTxsList TxnsDataBinary
Definition: PostgresDatabase.cpp:47
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::uint32_t
ripple::PostgresDatabaseImp::getTxHistory
std::vector< std::shared_ptr< Transaction > > getTxHistory(LedgerIndex startIndex) override
getTxHistory Returns the 20 most recent transactions starting from the given number.
Definition: PostgresDatabase.cpp:749
std::map
STL class.
ripple::PostgresDatabaseImp::getMaxLedgerSeq
std::optional< LedgerIndex > getMaxLedgerSeq() override
getMaxLedgerSeq Returns the maximum ledger sequence in the Ledgers table.
Definition: PostgresDatabase.cpp:503
ripple::range
ClosedInterval< T > range(T low, T high)
Create a closed range interval.
Definition: RangeSet.h:54
ripple::PostgresDatabaseImp::getLedgerInfoByIndex
std::optional< LedgerInfo > getLedgerInfoByIndex(LedgerIndex ledgerSeq) override
getLedgerInfoByIndex Returns a ledger by its sequence.
Definition: PostgresDatabase.cpp:624
ripple::PostgresDatabaseImp::getTxHashes
std::vector< uint256 > getTxHashes(LedgerIndex seq) override
getTxHashes Returns a vector of the hashes of transactions belonging to the ledger with the provided ...
Definition: PostgresDatabase.cpp:679
ripple::LedgerHeader::closeTime
NetClock::time_point closeTime
Definition: LedgerHeader.h:72
ripple::AccountTxResult
RelationalDatabase::AccountTxResult AccountTxResult
Definition: PostgresDatabase.cpp:45
ripple::LedgerHeader
Information about the notional ledger backing the view.
Definition: LedgerHeader.h:33
ripple::JobQueue
A pool of threads to perform work.
Definition: JobQueue.h:55
ripple::rpcINTERNAL
@ rpcINTERNAL
Definition: ErrorCodes.h:130
ripple::RelationalDatabase::AccountTxArgs::forward
bool forward
Definition: RelationalDatabase.h:101
std::string::substr
T substr(T... args)
ripple::PostgresDatabaseImp::isCaughtUp
bool isCaughtUp(std::string &reason) override
isCaughtUp returns whether the database is caught up with the network
Definition: PostgresDatabase.cpp:1054
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::LedgerHeader::validated
bool validated
Definition: LedgerHeader.h:59
ripple::LedgerHeader::drops
XRPAmount drops
Definition: LedgerHeader.h:54
ripple::LedgerHeader::closeTimeResolution
NetClock::duration closeTimeResolution
Definition: LedgerHeader.h:66
ripple::PostgresDatabaseImp::getCompleteLedgers
std::string getCompleteLedgers() override
getCompleteLedgers Returns a string which contains a list of completed ledgers.
Definition: PostgresDatabase.cpp:514
Json::Value::asUInt
UInt asUInt() const
Definition: json_value.cpp:545
ripple::PostgresDatabase
Definition: PostgresDatabase.h:27
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
ripple::LedgerHashPair::parentHash
uint256 parentHash
Definition: RelationalDatabase.h:39
std::get_if
T get_if(T... args)
ripple::PostgresDatabaseImp::getLedgerInfoByHash
std::optional< LedgerInfo > getLedgerInfoByHash(uint256 const &ledgerHash) override
getLedgerInfoByHash Returns the info of the ledger with given hash.
Definition: PostgresDatabase.cpp:636
std::visit
T visit(T... args)
ripple::PostgresDatabaseImp::pgPool_
std::shared_ptr< PgPool > pgPool_
Definition: PostgresDatabase.cpp:143
std::optional
std::stringstream::str
T str(T... args)
beast::Journal::debug
Stream debug() const
Definition: Journal.h:314
ripple::PostgresDatabaseImp::sweep
void sweep() override
sweep Sweeps the database.
Definition: PostgresDatabase.cpp:480
std::make_pair
T make_pair(T... args)
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
ripple::TxnsData
RelationalDatabase::AccountTxs TxnsData
Definition: PostgresDatabase.cpp:46
ripple::ClosedInterval
boost::icl::closed_interval< T > ClosedInterval
A closed interval over the domain T.
Definition: RangeSet.h:45
ripple::NetClock::duration
std::chrono::duration< rep, period > duration
Definition: chrono.h:69
ripple::RelationalDatabase::AccountTxArgs::ledger
std::optional< LedgerSpecifier > ledger
Definition: RelationalDatabase.h:99
ripple::base_uint::parseHex
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition: base_uint.h:496
ripple::Config::reportingReadOnly
bool reportingReadOnly() const
Definition: Config.h:362
std::unique_ptr
STL class.
ripple::loadLedgerHelper
std::shared_ptr< Ledger > loadLedgerHelper(LedgerInfo const &info, Application &app, bool acquire)
Definition: Ledger.cpp:1076
ripple::PostgresDatabaseImp::ledgerDbHasSpace
bool ledgerDbHasSpace(Config const &config) override
ledgerDbHasSpace Checks if the ledger database has available space.
Definition: PostgresDatabase.cpp:1036
ripple::NetClock::time_point
std::chrono::time_point< NetClock > time_point
Definition: chrono.h:70
std::string::data
T data(T... args)
std::exception::what
T what(T... args)
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::PostgresDatabaseImp::j_
beast::Journal j_
Definition: PostgresDatabase.cpp:142
std::variant
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
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:1151
ripple::PostgresDatabaseImp::writeLedgerAndTransactions
bool writeLedgerAndTransactions(LedgerInfo const &info, std::vector< AccountTransactionsData > const &accountTxData) override
writeLedgerAndTransactions Writes new ledger and transaction data into the database.
Definition: PostgresDatabase.cpp:539