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>
37 #ifdef RIPPLED_REPORTING
38 auto seq = PgQuery(pgPool)(
"SELECT min_ledger()");
41 JLOG(j.
error()) <<
"Error querying minimum ledger sequence.";
43 else if (!seq.isNull())
52 #ifdef RIPPLED_REPORTING
53 auto seq = PgQuery(pgPool)(
"SELECT max_ledger()");
54 if (seq && !seq.isNull())
55 return seq.asBigInt();
63 #ifdef RIPPLED_REPORTING
64 auto range = PgQuery(pgPool)(
"SELECT complete_ledgers()");
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";
106 #ifdef RIPPLED_REPORTING
107 auto log = app.
journal(
"Ledger");
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 ";
114 uint32_t expNumResults = 1;
116 if (
auto ledgerSeq = std::get_if<uint32_t>(&whichLedger))
120 else if (
auto ledgerHash = std::get_if<uint256>(&whichLedger))
122 sql << (
"WHERE ledger_hash = \'\\x" +
strHex(*ledgerHash) +
"\'");
128 expNumResults = minAndMax->second - minAndMax->first;
136 sql << (
"ORDER BY ledger_seq desc LIMIT 1");
140 JLOG(log.trace()) << __func__ <<
" : sql = " << sql.
str();
142 auto res = PgQuery(pgPool)(sql.
str().data());
145 JLOG(log.error()) << __func__ <<
" : Postgres response is null - sql = "
150 else if (res.status() != PGRES_TUPLES_OK)
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();
161 JLOG(log.trace()) << __func__ <<
" Postgres result msg : " << res.msg();
163 if (res.isNull() || res.ntuples() == 0)
165 JLOG(log.debug()) << __func__
166 <<
" : Ledger not found. sql = " << sql.
str();
169 else if (res.ntuples() > 0)
171 if (res.nfields() != 10)
173 JLOG(log.error()) << __func__
174 <<
" : Wrong number of fields in Postgres "
175 "response. Expected 10, but got "
176 << res.nfields() <<
" . sql = " << sql.
str();
182 for (
size_t i = 0; i < res.ntuples(); ++i)
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);
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";
215 info.
drops = totalCoins;
216 info.
closeTime = time_point{duration{closeTime}};
220 info.
seq = ledgerSeq;
247 [&infos, &app, &pgPool](
auto&& arg) {
251 assert(infos.
size() <= 1);
288 assert(infos.size() <= 1);
290 return infos[0].hash;
303 assert(infos.size() <= 1);
306 ledgerHash = infos[0].hash;
307 parentHash = infos[0].parentHash;
322 for (
auto& info : infos)
324 ret[info.seq] = {info.hash, info.parentHash};
337 #ifdef RIPPLED_REPORTING
338 auto log = app.
journal(
"Ledger");
341 "SELECT nodestore_hash"
342 " FROM transactions "
343 " WHERE ledger_seq = " +
345 auto res = PgQuery(pgPool)(query.
c_str());
349 JLOG(log.error()) << __func__
350 <<
" : Postgres response is null - query = " << query;
354 else if (res.status() != PGRES_TUPLES_OK)
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;
365 JLOG(log.trace()) << __func__ <<
" Postgres result msg : " << res.msg();
367 if (res.isNull() || res.ntuples() == 0)
369 JLOG(log.debug()) << __func__
370 <<
" : Ledger not found. query = " << query;
373 else if (res.ntuples() > 0)
375 if (res.nfields() != 1)
377 JLOG(log.error()) << __func__
378 <<
" : Wrong number of fields in Postgres "
379 "response. Expected 1, but got "
380 << res.nfields() <<
" . query = " << query;
386 JLOG(log.trace()) << __func__ <<
" : result = " << res.c_str()
387 <<
" : query = " << query;
388 for (
size_t i = 0; i < res.ntuples(); ++i)
390 char const* nodestoreHash = res.
c_str(i, 0);
392 if (!hash.
parseHex(nodestoreHash + 2))
399 return nodestoreHashes;
402 #ifdef RIPPLED_REPORTING
403 enum class DataFormat { binary, expanded };
412 if (format == DataFormat::binary)
420 for (
size_t i = 0; i < txns.size(); ++i)
422 auto& [txn, meta] = txns[i];
423 if (format == DataFormat::binary)
425 auto& transactions = std::get<TxnsDataBinary>(ret);
426 Serializer txnSer = txn->getSerializer();
427 Serializer metaSer = meta->getSerializer();
429 Blob txnBlob = txnSer.getData();
430 Blob metaBlob = metaSer.getData();
436 auto& transactions = std::get<TxnsData>(ret);
438 auto txnRet = std::make_shared<Transaction>(txn, reason, app);
439 txnRet->setLedger(ledgerSequences[i]);
441 auto txMeta = std::make_shared<TxMeta>(
442 txnRet->getID(), ledgerSequences[i], *meta);
450 processAccountTxStoredProcedureResult(
457 ret.limit = args.limit;
461 if (result.
isMember(
"transactions"))
465 for (
auto& t : result[
"transactions"])
467 if (t.isMember(
"ledger_seq") && t.isMember(
"nodestore_hash"))
469 uint32_t ledgerSequence = t[
"ledger_seq"].asUInt();
471 t[
"nodestore_hash"].asString();
472 nodestoreHashHex.
erase(0, 2);
474 if (!nodestoreHash.parseHex(nodestoreHashHex))
477 if (nodestoreHash.isNonZero())
479 ledgerSequences.
push_back(ledgerSequence);
480 nodestoreHashes.
push_back(nodestoreHash);
485 return {ret, {
rpcINTERNAL,
"nodestoreHash is zero"}};
491 return {ret, {
rpcINTERNAL,
"missing postgres fields"}};
495 assert(nodestoreHashes.
size() == ledgerSequences.
size());
500 args.binary ? DataFormat::binary : DataFormat::expanded);
502 JLOG(j.
trace()) << __func__ <<
" : processed db results";
504 if (result.isMember(
"marker"))
506 auto& marker = result[
"marker"];
507 assert(marker.isMember(
"ledger"));
508 assert(marker.isMember(
"seq"));
510 marker[
"ledger"].asUInt(), marker[
"seq"].asUInt()};
512 assert(result.isMember(
"ledger_index_min"));
513 assert(result.isMember(
"ledger_index_max"));
515 result[
"ledger_index_min"].asUInt(),
516 result[
"ledger_index_max"].asUInt()};
519 else if (result.isMember(
"error"))
522 << __func__ <<
" : error = " << result[
"error"].asString();
529 return {ret, {
rpcINTERNAL,
"unexpected Postgres response"}};
534 JLOG(j.
debug()) << __func__ <<
" : "
535 <<
"Caught exception : " << e.
what();
548 #ifdef RIPPLED_REPORTING
551 char const*& command = dbParams.first;
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)";
559 values[1] = args.
forward ?
"true" :
"false";
562 if (args.
limit == 0 || args.
limit > page_length)
569 if (
auto range = std::get_if<LedgerRange>(&args.
ledger.value()))
574 else if (
auto hash = std::get_if<LedgerHash>(&args.
ledger.value()))
576 values[5] = (
"\\x" +
strHex(*hash));
579 auto sequence = std::get_if<LedgerSequence>(&args.
ledger.value()))
583 else if (std::get_if<LedgerShortcut>(&args.
ledger.value()))
590 JLOG(j.
error()) <<
"doAccountTxStoredProcedure - "
591 <<
"Error parsing ledger args";
601 for (
size_t i = 0; i < values.
size(); ++i)
604 << (values[i] ? values[i].value() :
"null");
607 auto res = PgQuery(pgPool)(dbParams);
610 JLOG(j.
error()) << __func__
611 <<
" : Postgres response is null - account = "
616 else if (res.status() != PGRES_TUPLES_OK)
618 JLOG(j.
error()) << __func__
619 <<
" : Postgres response should have been "
620 "PGRES_TUPLES_OK but instead was "
621 << res.status() <<
" - msg = " << res.msg()
627 JLOG(j.
trace()) << __func__ <<
" Postgres result msg : " << res.msg();
628 if (res.isNull() || res.ntuples() == 0)
630 JLOG(j.
debug()) << __func__
631 <<
" : No data returned from Postgres : account = "
638 char const* resultStr = res.c_str();
639 JLOG(j.
trace()) << __func__ <<
" : "
640 <<
"postgres result = " << resultStr
645 bool success = reader.
parse(resultStr, resultStr + strlen(resultStr), v);
648 return processAccountTxStoredProcedureResult(args, v, app, j);
653 return {{}, {
rpcINTERNAL,
"Failed to deserialize Postgres result"}};
662 #ifdef RIPPLED_REPORTING
663 auto baseCmd = boost::format(R
"(SELECT tx('%s');)");
668 auto res = PgQuery(pgPool)(sql.
data());
674 <<
" : Postgres response is null - tx ID = " <<
strHex(
id);
678 else if (res.status() != PGRES_TUPLES_OK)
682 <<
" : Postgres response should have been "
683 "PGRES_TUPLES_OK but instead was "
684 << res.status() <<
" - msg = " << res.msg()
685 <<
" - tx ID = " <<
strHex(
id);
691 << __func__ <<
" Postgres result msg : " << res.msg();
692 if (res.isNull() || res.ntuples() == 0)
696 <<
" : No data returned from Postgres : tx ID = " <<
strHex(
id);
702 char const* resultStr = res.c_str();
704 <<
"postgres result = " << resultStr;
708 bool success = reader.
parse(resultStr, resultStr + strlen(resultStr), v);
717 uint32_t ledgerSeq = v[
"ledger_seq"].
asUInt();
724 v[
"min_seq"].asUInt(), v[
"max_seq"].asUInt())};
731 Throw<std::runtime_error>(
732 "Transaction::Locate - Invalid Postgres response");
736 #ifdef RIPPLED_REPORTING
738 writeToLedgersDB(LedgerInfo
const& info, PgQuery& pgQuery,
beast::Journal& j)
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'))");
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 %
751 JLOG(j.
trace()) << __func__ <<
" : "
753 <<
"query string = " << ledgerInsert;
755 auto res = pgQuery(ledgerInsert.data());
768 #ifdef RIPPLED_REPORTING
769 JLOG(j.
debug()) << __func__ <<
" : "
770 <<
"Beginning write to Postgres";
777 auto res = pg(
"BEGIN");
778 if (!res || res.status() != PGRES_COMMAND_OK)
781 msg <<
"bulkWriteToTable : Postgres insert error: " << res.msg();
782 Throw<std::runtime_error>(msg.
str());
788 if (!writeToLedgersDB(info, pg, j))
790 JLOG(j.
warn()) << __func__ <<
" : "
791 <<
"Failed to write to ledgers database.";
797 for (
auto const& data : accountTxData)
801 auto idx = data.transactionIndex;
802 auto ledgerSeq = data.ledgerSequence;
806 << txHash <<
'\t' <<
"\\\\x" << nodestoreHash
809 for (
auto const& a : data.accounts)
812 accountTransactionsCopyBuffer
818 pg.bulkInsert(
"transactions", transactionsCopyBuffer.
str());
820 "account_transactions", accountTransactionsCopyBuffer.
str());
823 if (!res || res.status() != PGRES_COMMAND_OK)
826 msg <<
"bulkWriteToTable : Postgres insert error: " << res.msg();
828 Throw<std::runtime_error>(msg.
str());
831 JLOG(j.
info()) << __func__ <<
" : "
832 <<
"Successfully wrote to Postgres";
837 JLOG(j.
error()) << __func__ <<
"Caught exception writing to Postgres : "
856 #ifdef RIPPLED_REPORTING
860 Throw<std::runtime_error>(
861 "called getTxHistory but not in reporting mode");
865 boost::format(
"SELECT nodestore_hash, ledger_seq "
867 " ORDER BY ledger_seq DESC LIMIT 20 "
871 auto res = PgQuery(pgPool)(sql.
data());
875 JLOG(j.
error()) << __func__
876 <<
" : Postgres response is null - sql = " << sql;
880 else if (res.status() != PGRES_TUPLES_OK)
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;
891 JLOG(j.
trace()) << __func__ <<
" Postgres result msg : " << res.msg();
893 if (res.isNull() || res.ntuples() == 0)
895 JLOG(j.
debug()) << __func__ <<
" : Empty postgres response";
899 else if (res.ntuples() > 0)
901 if (res.nfields() != 2)
903 JLOG(j.
error()) << __func__
904 <<
" : Wrong number of fields in Postgres "
905 "response. Expected 1, but got "
906 << res.nfields() <<
" . sql = " << sql;
912 JLOG(j.
trace()) << __func__ <<
" : Postgres result = " << res.c_str();
916 for (
size_t i = 0; i < res.ntuples(); ++i)
919 if (!hash.
parseHex(res.c_str(i, 0) + 2))
922 ledgerSequences.
push_back(res.asBigInt(i, 1));
926 for (
size_t i = 0; i < txns.size(); ++i)
928 auto const& [sttx, meta] = txns[i];
932 auto txn = std::make_shared<Transaction>(sttx, reason, app);
933 txn->setLedger(ledgerSequences[i]);