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/PendingSaves.h>
24 #include <ripple/app/ledger/TransactionMaster.h>
25 #include <ripple/app/misc/Manifest.h>
26 #include <ripple/app/rdb/RelationalDBInterface.h>
27 #include <ripple/app/rdb/RelationalDBInterface_nodes.h>
28 #include <ripple/basics/BasicConfig.h>
29 #include <ripple/basics/StringUtilities.h>
30 #include <ripple/core/DatabaseCon.h>
31 #include <ripple/core/SociDB.h>
32 #include <ripple/json/to_string.h>
33 #include <boost/algorithm/string.hpp>
34 #include <boost/range/adaptor/transformed.hpp>
35 #include <soci/sqlite3/soci-sqlite3.h>
49 "Need to modify switch statement if enum is modified");
55 return "Transactions";
57 return "AccountTransactions";
71 auto lgr{std::make_unique<DatabaseCon>(
73 lgr->getSession() << boost::str(
74 boost::format(
"PRAGMA cache_size=-%d;") %
80 auto tx{std::make_unique<DatabaseCon>(
82 tx->getSession() << boost::str(
83 boost::format(
"PRAGMA cache_size=-%d;") %
95 (tx->getSession().prepare
96 << (
"PRAGMA table_info(AccountTransactions);"),
101 soci::into(dflt_value, ind),
109 return {std::move(lgr), std::move(tx),
false};
114 return {std::move(lgr), std::move(tx),
true};
117 return {std::move(lgr), {},
true};
125 boost::optional<LedgerIndex> m;
126 session << query, soci::into(m);
135 boost::optional<LedgerIndex> m;
136 session << query, soci::into(m);
143 session <<
"DELETE FROM " <<
to_string(type)
144 <<
" WHERE LedgerSeq == " << ledgerSeq <<
";";
149 soci::session& session,
153 session <<
"DELETE FROM " <<
to_string(type) <<
" WHERE LedgerSeq < "
161 session <<
"SELECT COUNT(*) AS rows "
169 RelationalDBInterface::CountMinMax
173 session <<
"SELECT COUNT(*) AS rows, "
174 "MIN(LedgerSeq) AS first, "
175 "MAX(LedgerSeq) AS last "
192 auto j = app.
journal(
"Ledger");
196 JLOG(j.trace()) <<
"saveValidatedLedger " << (
current ?
"" :
"fromAcquire ")
201 JLOG(j.fatal()) <<
"AH is zero: " <<
getJson({*ledger, {}});
209 JLOG(j.fatal()) <<
"saveAcceptedLedger: seq=" << seq
231 aLedger = std::make_shared<AcceptedLedger>(ledger, app);
238 JLOG(j.warn()) <<
"An accepted ledger was missing nodes";
247 static boost::format deleteLedger(
248 "DELETE FROM Ledgers WHERE LedgerSeq = %u;");
249 static boost::format deleteTrans1(
250 "DELETE FROM Transactions WHERE LedgerSeq = %u;");
251 static boost::format deleteTrans2(
252 "DELETE FROM AccountTransactions WHERE LedgerSeq = %u;");
253 static boost::format deleteAcctTrans(
254 "DELETE FROM AccountTransactions WHERE TransID = '%s';");
258 *db << boost::str(deleteLedger % seq);
265 soci::transaction tr(*db);
267 *db << boost::str(deleteTrans1 % seq);
268 *db << boost::str(deleteTrans2 % seq);
272 for (
auto const& [_, acceptedLedgerTx] : aLedger->getMap())
283 auto const& accts = acceptedLedgerTx->getAffected();
288 "INSERT INTO AccountTransactions "
289 "(TransID, Account, LedgerSeq, TxnSeq) VALUES ");
297 for (
auto const& account : accts)
317 JLOG(j.trace()) <<
"ActTx: " << sql;
320 else if (
auto const sleTxn = acceptedLedgerTx->getTxn();
325 JLOG(j.warn()) <<
"Transaction in ledger " << seq
326 <<
" affects no accounts";
332 acceptedLedgerTx->getTxn()->getMetaSQL(
333 seq, acceptedLedgerTx->getEscMeta()) +
344 R
"sql(INSERT OR REPLACE INTO Ledgers
345 (LedgerHash,LedgerSeq,PrevHash,TotalCoins,ClosingTime,PrevClosingTime,
346 CloseTimeRes,CloseFlags,AccountSetHash,TransSetHash)
348 (:ledgerHash,:ledgerSeq,:prevHash,:totalCoins,:closingTime,:prevClosingTime,
349 :closeTimeRes,:closeFlags,:accountSetHash,:transSetHash);)sql");
353 soci::transaction tr(*db);
358 auto const closeTime =
360 auto const parentCloseTime =
362 auto const closeTimeResolution =
368 *db << addLedger, soci::use(hash), soci::use(seq),
369 soci::use(parentHash), soci::use(drops), soci::use(closeTime),
370 soci::use(parentCloseTime), soci::use(closeTimeResolution),
371 soci::use(closeFlags), soci::use(accountHash),
391 soci::session& session,
396 boost::optional<std::string> hash, parentHash, accountHash, txHash;
397 boost::optional<std::uint64_t> seq, drops, closeTime, parentCloseTime,
398 closeTimeResolution, closeFlags;
402 "LedgerHash, PrevHash, AccountSetHash, TransSetHash, "
404 "ClosingTime, PrevClosingTime, CloseTimeRes, CloseFlags,"
405 "LedgerSeq FROM Ledgers " +
408 session << sql, soci::into(hash), soci::into(parentHash),
409 soci::into(accountHash), soci::into(txHash), soci::into(drops),
410 soci::into(closeTime), soci::into(parentCloseTime),
411 soci::into(closeTimeResolution), soci::into(closeFlags),
414 if (!session.got_data())
416 JLOG(j.
debug()) <<
"Ledger not found: " << sqlSuffix;
425 if (hash && !info.hash.parseHex(*hash))
427 JLOG(j.
debug()) <<
"Hash parse error for ledger: " << sqlSuffix;
431 if (parentHash && !info.parentHash.parseHex(*parentHash))
433 JLOG(j.
debug()) <<
"parentHash parse error for ledger: " << sqlSuffix;
437 if (accountHash && !info.accountHash.parseHex(*accountHash))
439 JLOG(j.
debug()) <<
"accountHash parse error for ledger: " << sqlSuffix;
443 if (txHash && !info.txHash.parseHex(*txHash))
445 JLOG(j.
debug()) <<
"txHash parse error for ledger: " << sqlSuffix;
449 info.seq = rangeCheckedCast<std::uint32_t>(seq.value_or(0));
450 info.drops =
drops.value_or(0);
451 info.closeTime = time_point{duration{closeTime.value_or(0)}};
452 info.parentCloseTime = time_point{duration{parentCloseTime.value_or(0)}};
453 info.closeFlags = closeFlags.value_or(0);
454 info.closeTimeResolution = duration{closeTimeResolution.value_or(0)};
461 soci::session& session,
466 s <<
"WHERE LedgerSeq = " << ledgerSeq;
474 s <<
"ORDER BY LedgerSeq DESC LIMIT 1";
480 soci::session& session,
486 " ORDER BY LedgerSeq ASC LIMIT 1";
492 soci::session& session,
498 " ORDER BY LedgerSeq DESC LIMIT 1";
504 soci::session& session,
509 s <<
"WHERE LedgerHash = '" << ledgerHash <<
"'";
519 "SELECT LedgerHash FROM Ledgers INDEXED BY SeqLedger WHERE LedgerSeq='";
520 sql.
append(beast::lexicalCastThrow<std::string>(ledgerIndex));
526 boost::optional<std::string> lh;
527 session << sql, soci::into(lh);
529 if (!session.got_data() || !lh)
537 if (!ret.parseHex(hash))
545 soci::session& session,
550 boost::optional<std::string> lhO, phO;
552 session <<
"SELECT LedgerHash,PrevHash FROM Ledgers "
553 "INDEXED BY SeqLedger WHERE LedgerSeq = :ls;",
554 soci::into(lhO), soci::into(phO), soci::use(ledgerIndex);
558 auto stream = j.
trace();
559 JLOG(stream) <<
"Don't have ledger " << ledgerIndex;
563 LedgerHashPair hashes;
564 if (!hashes.ledgerHash.parseHex(*lhO) || !hashes.parentHash.parseHex(*phO))
567 JLOG(stream) <<
"Error parse hashes for ledger " << ledgerIndex;
576 soci::session& session,
582 "SELECT LedgerSeq,LedgerHash,PrevHash FROM Ledgers WHERE LedgerSeq >= ";
583 sql.
append(beast::lexicalCastThrow<std::string>(minSeq));
584 sql.
append(
" AND LedgerSeq <= ");
585 sql.
append(beast::lexicalCastThrow<std::string>(maxSeq));
591 boost::optional<std::string> ph;
593 (session.prepare << sql,
602 LedgerHashPair& hashes = res[rangeCheckedCast<LedgerIndex>(ls)];
603 if (!hashes.ledgerHash.parseHex(lh))
605 JLOG(j.
warn()) <<
"Error parsed hash for ledger seq: " << ls;
609 JLOG(j.
warn()) <<
"Null prev hash for ledger seq: " << ls;
611 else if (!hashes.parentHash.parseHex(*ph))
613 JLOG(j.
warn()) <<
"Error parsed prev hash for ledger seq: " << ls;
621 soci::session& session,
629 "SELECT LedgerSeq, Status, RawTxn "
630 "FROM Transactions ORDER BY LedgerSeq DESC LIMIT %u,%u;") %
631 startIndex % quantity);
638 boost::optional<std::uint64_t> ledgerSeq;
639 boost::optional<std::string>
status;
640 soci::blob sociRawTxnBlob(session);
645 (session.prepare << sql,
646 soci::into(ledgerSeq),
648 soci::into(sociRawTxnBlob, rti));
653 if (soci::i_ok == rti)
654 convert(sociRawTxnBlob, rawTxn);
659 ledgerSeq,
status, rawTxn, app))
668 session <<
"SELECT COUNT(*) FROM Transactions;", soci::into(total);
700 RelationalDBInterface::AccountTxOptions
const& options,
716 else if (options.limit == UINT32_MAX)
718 numberOfResults = binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH;
720 else if (!options.bUnlimited)
723 binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH, options.limit);
727 numberOfResults = options.limit;
732 if (numberOfResults <= *limit_used)
735 numberOfResults -= *limit_used;
741 if (options.maxLedger)
743 maxClause = boost::str(
744 boost::format(
"AND AccountTransactions.LedgerSeq <= '%u'") %
748 if (options.minLedger)
750 minClause = boost::str(
751 boost::format(
"AND AccountTransactions.LedgerSeq >= '%u'") %
759 boost::format(
"SELECT %s FROM AccountTransactions "
760 "WHERE Account = '%s' %s %s LIMIT %u, %u;") %
761 selection % app.accountIDCache().toBase58(options.account) %
762 maxClause % minClause %
763 beast::lexicalCastThrow<std::string>(options.offset) %
764 beast::lexicalCastThrow<std::string>(numberOfResults));
769 "AccountTransactions INNER JOIN Transactions "
770 "ON Transactions.TransID = AccountTransactions.TransID "
771 "WHERE Account = '%s' %s %s "
772 "ORDER BY AccountTransactions.LedgerSeq %s, "
773 "AccountTransactions.TxnSeq %s, AccountTransactions.TransID %s "
775 selection % app.accountIDCache().toBase58(options.account) %
776 maxClause % minClause % (descending ?
"DESC" :
"ASC") %
777 (descending ?
"DESC" :
"ASC") % (descending ?
"DESC" :
"ASC") %
778 beast::lexicalCastThrow<std::string>(options.offset) %
779 beast::lexicalCastThrow<std::string>(numberOfResults));
780 JLOG(j.
trace()) <<
"txSQL query: " << sql;
805 soci::session& session,
807 LedgerMaster& ledgerMaster,
808 RelationalDBInterface::AccountTxOptions
const& options,
817 "AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta",
830 boost::optional<std::uint64_t> ledgerSeq;
831 boost::optional<std::string>
status;
832 soci::blob sociTxnBlob(session), sociTxnMetaBlob(session);
833 soci::indicator rti, tmi;
834 Blob rawTxn, txnMeta;
837 (session.prepare << sql,
838 soci::into(ledgerSeq),
840 soci::into(sociTxnBlob, rti),
841 soci::into(sociTxnMetaBlob, tmi));
846 if (soci::i_ok == rti)
851 if (soci::i_ok == tmi)
852 convert(sociTxnMetaBlob, txnMeta);
862 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0));
865 <<
"Recovering ledger " << seq <<
", txn " << txn->getID();
875 std::make_shared<TxMeta>(
876 txn->getID(), txn->getLedger(), txnMeta));
881 if (!total && limit_used)
883 RelationalDBInterface::AccountTxOptions opt = options;
886 app,
"COUNT(*)", opt, limit_used, descending,
false,
false, j);
888 session << sql1, soci::into(total);
899 soci::session& session,
901 LedgerMaster& ledgerMaster,
902 RelationalDBInterface::AccountTxOptions
const& options,
907 session, app, ledgerMaster, options, limit_used,
false, j);
912 soci::session& session,
914 LedgerMaster& ledgerMaster,
915 RelationalDBInterface::AccountTxOptions
const& options,
920 session, app, ledgerMaster, options, limit_used,
true, j);
944 soci::session& session,
946 RelationalDBInterface::AccountTxOptions
const& options,
955 "AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta",
969 boost::optional<std::uint64_t> ledgerSeq;
970 boost::optional<std::string>
status;
971 soci::blob sociTxnBlob(session), sociTxnMetaBlob(session);
972 soci::indicator rti, tmi;
975 (session.prepare << sql,
976 soci::into(ledgerSeq),
978 soci::into(sociTxnBlob, rti),
979 soci::into(sociTxnMetaBlob, tmi));
985 if (soci::i_ok == rti)
988 if (soci::i_ok == tmi)
989 convert(sociTxnMetaBlob, txnMeta);
992 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0));
994 ret.
emplace_back(std::move(rawTxn), std::move(txnMeta), seq);
998 if (!total && limit_used)
1000 RelationalDBInterface::AccountTxOptions opt = options;
1003 app,
"COUNT(*)", opt, limit_used, descending,
true,
false, j);
1005 session << sql1, soci::into(total);
1011 return {ret, total};
1016 soci::session& session,
1018 RelationalDBInterface::AccountTxOptions
const& options,
1022 return getAccountTxsB(session, app, options, limit_used,
false, j);
1027 soci::session& session,
1029 RelationalDBInterface::AccountTxOptions
const& options,
1033 return getAccountTxsB(session, app, options, limit_used,
true, j);
1060 soci::session& session,
1061 AccountIDCache
const& idCache,
1066 RelationalDBInterface::AccountTxPageOptions
const& options,
1073 bool lookingForMarker = options.marker.has_value();
1077 if (options.limit == 0 || options.limit == UINT32_MAX ||
1078 (options.limit > page_length && !options.bAdmin))
1079 numberOfResults = page_length;
1081 numberOfResults = options.limit;
1083 if (numberOfResults < limit_used)
1084 return {options.marker, -1};
1085 numberOfResults -= limit_used;
1095 if (lookingForMarker)
1097 findLedger = options.marker->ledgerSeq;
1098 findSeq = options.marker->txnSeq;
1103 newmarker = options.marker;
1106 R
"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
1107 Status,RawTxn,TxnMeta
1108 FROM AccountTransactions INNER JOIN Transactions
1109 ON Transactions.TransID = AccountTransactions.TransID
1110 AND AccountTransactions.Account = '%s' WHERE
1117 const char*
const order =
forward ?
"ASC" :
"DESC";
1119 if (findLedger == 0)
1123 prefix + (R
"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u'
1124 ORDER BY AccountTransactions.LedgerSeq %s,
1125 AccountTransactions.TxnSeq %s
1127 idCache.toBase58(options.account) % options.minLedger %
1128 options.maxLedger % order % order % queryLimit);
1134 forward ? findLedger + 1 : options.minLedger;
1136 forward ? options.maxLedger : findLedger - 1;
1138 auto b58acct = idCache.toBase58(options.account);
1141 R
"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
1142 Status,RawTxn,TxnMeta
1143 FROM AccountTransactions, Transactions WHERE
1144 (AccountTransactions.TransID = Transactions.TransID AND
1145 AccountTransactions.Account = '%s' AND
1146 AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u')
1148 (AccountTransactions.TransID = Transactions.TransID AND
1149 AccountTransactions.Account = '%s' AND
1150 AccountTransactions.LedgerSeq = '%u' AND
1151 AccountTransactions.TxnSeq %s '%u')
1152 ORDER BY AccountTransactions.LedgerSeq %s,
1153 AccountTransactions.TxnSeq %s
1156 b58acct % minLedger % maxLedger % b58acct % findLedger % compare %
1157 findSeq % order % order % queryLimit);
1165 boost::optional<std::uint64_t> ledgerSeq;
1166 boost::optional<std::uint32_t> txnSeq;
1167 boost::optional<std::string>
status;
1168 soci::blob txnData(session);
1169 soci::blob txnMeta(session);
1170 soci::indicator dataPresent, metaPresent;
1172 soci::statement st =
1173 (session.prepare << sql,
1174 soci::into(ledgerSeq),
1177 soci::into(txnData, dataPresent),
1178 soci::into(txnMeta, metaPresent));
1184 if (lookingForMarker)
1186 if (findLedger == ledgerSeq.value_or(0) &&
1187 findSeq == txnSeq.value_or(0))
1189 lookingForMarker =
false;
1194 else if (numberOfResults == 0)
1197 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0)),
1202 if (dataPresent == soci::i_ok)
1207 if (metaPresent == soci::i_ok)
1213 if (rawMeta.size() == 0)
1214 onUnsavedLedger(ledgerSeq.value_or(0));
1219 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0)),
1222 std::move(rawMeta));
1236 return {newmarker, total};
1241 soci::session& session,
1264 soci::session& session,
1287 soci::session& session,
1294 "SELECT LedgerSeq,Status,RawTxn,TxnMeta "
1295 "FROM Transactions WHERE TransID='";
1301 boost::optional<std::uint64_t> ledgerSeq;
1302 boost::optional<std::string> status;
1303 Blob rawTxn, rawMeta;
1305 soci::blob sociRawTxnBlob(session), sociRawMetaBlob(session);
1306 soci::indicator txn, meta;
1308 session << sql, soci::into(ledgerSeq), soci::into(status),
1309 soci::into(sociRawTxnBlob, txn), soci::into(sociRawMetaBlob, meta);
1311 auto const got_data = session.got_data();
1313 if ((!got_data || txn != soci::i_ok || meta != soci::i_ok) && !
range)
1319 soci::indicator rti;
1322 <<
"SELECT COUNT(DISTINCT LedgerSeq) FROM Transactions WHERE "
1323 "LedgerSeq BETWEEN "
1324 <<
range->first() <<
" AND " <<
range->last() <<
";",
1325 soci::into(count, rti);
1327 if (!session.got_data() || rti != soci::i_ok)
1330 return count == (
range->last() -
range->first() + 1)
1335 convert(sociRawTxnBlob, rawTxn);
1336 convert(sociRawMetaBlob, rawMeta);
1345 return std::pair{std::move(txn),
nullptr};
1348 rangeCheckedCast<std::uint32_t>(ledgerSeq.value());
1350 auto txMeta = std::make_shared<TxMeta>(
id, inLedger, rawMeta);
1352 return std::pair{std::move(txn), std::move(txMeta)};
1356 JLOG(app.journal(
"Ledger").warn())
1357 <<
"Unable to deserialize transaction from raw SQL value. Error: "
1369 boost::filesystem::space_info
space =
1370 boost::filesystem::space(config.legacy(
"database_path"));
1374 JLOG(j.
fatal()) <<
"Remaining free disk space is less than 512MB";
1378 if (config.useTxTables())
1381 boost::filesystem::path dbPath = dbSetup.dataDir /
TxDBName;
1382 boost::system::error_code ec;
1384 boost::filesystem::file_size(dbPath, ec);
1388 <<
"Error checking transaction db file size: " << ec.message();
1392 static auto const pageSize = [&] {
1394 session <<
"PRAGMA page_size;", soci::into(ps);
1397 static auto const maxPages = [&] {
1399 session <<
"PRAGMA max_page_count;", soci::into(mp);
1403 session <<
"PRAGMA page_count;", soci::into(pageCount);
1406 safe_cast<std::uint64_t>(freePages) * pageSize;
1408 <<
"Transaction DB pathname: " << dbPath.string()
1409 <<
"; file size: " << dbSize.
value_or(-1) <<
" bytes"
1410 <<
"; SQLite page size: " << pageSize <<
" bytes"
1411 <<
"; Free pages: " << freePages <<
"; Free space: " << freeSpace
1413 <<
"Note that this does not take into account available disk "
1419 <<
"Free SQLite space for transaction db is less than "
1420 "512MB. To fix this, rippled must be executed with the "
1421 "vacuum parameter before restarting. "
1422 "Note that this activity can take multiple days, "
1423 "depending on database size.";