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/RelationalDatabase.h>
27 #include <ripple/app/rdb/backend/detail/Node.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>
50 "Need to modify switch statement if enum is modified");
57 return "Transactions";
59 return "AccountTransactions";
73 auto lgr{std::make_unique<DatabaseCon>(
75 lgr->getSession() << boost::str(
76 boost::format(
"PRAGMA cache_size=-%d;") %
82 auto tx{std::make_unique<DatabaseCon>(
84 tx->getSession() << boost::str(
85 boost::format(
"PRAGMA cache_size=-%d;") %
97 (tx->getSession().prepare
98 << (
"PRAGMA table_info(AccountTransactions);"),
103 soci::into(dflt_value, ind),
111 return {std::move(lgr), std::move(tx),
false};
116 return {std::move(lgr), std::move(tx),
true};
119 return {std::move(lgr), {},
true};
127 boost::optional<LedgerIndex> m;
128 session << query, soci::into(m);
137 boost::optional<LedgerIndex> m;
138 session << query, soci::into(m);
145 session <<
"DELETE FROM " <<
to_string(type)
146 <<
" WHERE LedgerSeq == " << ledgerSeq <<
";";
151 soci::session& session,
155 session <<
"DELETE FROM " <<
to_string(type) <<
" WHERE LedgerSeq < "
163 session <<
"SELECT COUNT(*) AS rows "
175 session <<
"SELECT COUNT(*) AS rows, "
176 "MIN(LedgerSeq) AS first, "
177 "MAX(LedgerSeq) AS last "
194 auto j = app.
journal(
"Ledger");
195 auto seq = ledger->info().seq;
198 JLOG(j.trace()) <<
"saveValidatedLedger " << (
current ?
"" :
"fromAcquire ")
201 if (!ledger->info().accountHash.isNonZero())
203 JLOG(j.fatal()) <<
"AH is zero: " <<
getJson({*ledger, {}});
207 if (ledger->info().accountHash != ledger->stateMap().getHash().as_uint256())
209 JLOG(j.fatal()) <<
"sAL: " << ledger->info().accountHash
210 <<
" != " << ledger->stateMap().getHash();
211 JLOG(j.fatal()) <<
"saveAcceptedLedger: seq=" << seq
216 assert(ledger->info().txHash == ledger->txMap().getHash().as_uint256());
222 addRaw(ledger->info(), s);
233 aLedger = std::make_shared<AcceptedLedger>(ledger, app);
235 ledger->info().hash, aLedger);
240 JLOG(j.warn()) <<
"An accepted ledger was missing nodes";
249 static boost::format deleteLedger(
250 "DELETE FROM Ledgers WHERE LedgerSeq = %u;");
251 static boost::format deleteTrans1(
252 "DELETE FROM Transactions WHERE LedgerSeq = %u;");
253 static boost::format deleteTrans2(
254 "DELETE FROM AccountTransactions WHERE LedgerSeq = %u;");
255 static boost::format deleteAcctTrans(
256 "DELETE FROM AccountTransactions WHERE TransID = '%s';");
260 *db << boost::str(deleteLedger % seq);
267 soci::transaction tr(*db);
269 *db << boost::str(deleteTrans1 % seq);
270 *db << boost::str(deleteTrans2 % seq);
274 for (
auto const& acceptedLedgerTx : *aLedger)
284 auto const& accts = acceptedLedgerTx->getAffected();
289 "INSERT INTO AccountTransactions "
290 "(TransID, Account, LedgerSeq, TxnSeq) VALUES ");
298 for (
auto const& account : accts)
318 JLOG(j.trace()) <<
"ActTx: " << sql;
321 else if (
auto const& sleTxn = acceptedLedgerTx->getTxn();
326 JLOG(j.warn()) <<
"Transaction in ledger " << seq
327 <<
" affects no accounts";
333 acceptedLedgerTx->getTxn()->getMetaSQL(
334 seq, acceptedLedgerTx->getEscMeta()) +
345 R
"sql(INSERT OR REPLACE INTO Ledgers
346 (LedgerHash,LedgerSeq,PrevHash,TotalCoins,ClosingTime,PrevClosingTime,
347 CloseTimeRes,CloseFlags,AccountSetHash,TransSetHash)
349 (:ledgerHash,:ledgerSeq,:prevHash,:totalCoins,:closingTime,:prevClosingTime,
350 :closeTimeRes,:closeFlags,:accountSetHash,:transSetHash);)sql");
354 soci::transaction tr(*db);
356 auto const hash =
to_string(ledger->info().hash);
357 auto const parentHash =
to_string(ledger->info().parentHash);
358 auto const drops =
to_string(ledger->info().drops);
359 auto const closeTime =
360 ledger->info().closeTime.time_since_epoch().count();
361 auto const parentCloseTime =
362 ledger->info().parentCloseTime.time_since_epoch().count();
363 auto const closeTimeResolution =
364 ledger->info().closeTimeResolution.count();
365 auto const closeFlags = ledger->info().closeFlags;
366 auto const accountHash =
to_string(ledger->info().accountHash);
367 auto const txHash =
to_string(ledger->info().txHash);
369 *db << addLedger, soci::use(hash), soci::use(seq),
370 soci::use(parentHash), soci::use(drops), soci::use(closeTime),
371 soci::use(parentCloseTime), soci::use(closeTimeResolution),
372 soci::use(closeFlags), soci::use(accountHash),
392 soci::session& session,
397 boost::optional<std::string> hash, parentHash, accountHash, txHash;
398 boost::optional<std::uint64_t> seq, drops, closeTime, parentCloseTime,
399 closeTimeResolution, closeFlags;
403 "LedgerHash, PrevHash, AccountSetHash, TransSetHash, "
405 "ClosingTime, PrevClosingTime, CloseTimeRes, CloseFlags,"
406 "LedgerSeq FROM Ledgers " +
409 session << sql, soci::into(hash), soci::into(parentHash),
410 soci::into(accountHash), soci::into(txHash), soci::into(drops),
411 soci::into(closeTime), soci::into(parentCloseTime),
412 soci::into(closeTimeResolution), soci::into(closeFlags),
415 if (!session.got_data())
417 JLOG(j.
debug()) <<
"Ledger not found: " << sqlSuffix;
426 if (hash && !info.hash.parseHex(*hash))
428 JLOG(j.
debug()) <<
"Hash parse error for ledger: " << sqlSuffix;
432 if (parentHash && !info.parentHash.parseHex(*parentHash))
434 JLOG(j.
debug()) <<
"parentHash parse error for ledger: " << sqlSuffix;
438 if (accountHash && !info.accountHash.parseHex(*accountHash))
440 JLOG(j.
debug()) <<
"accountHash parse error for ledger: " << sqlSuffix;
444 if (txHash && !info.txHash.parseHex(*txHash))
446 JLOG(j.
debug()) <<
"txHash parse error for ledger: " << sqlSuffix;
450 info.seq = rangeCheckedCast<std::uint32_t>(seq.value_or(0));
451 info.drops =
drops.value_or(0);
452 info.closeTime = time_point{duration{closeTime.value_or(0)}};
453 info.parentCloseTime = time_point{duration{parentCloseTime.value_or(0)}};
454 info.closeFlags = closeFlags.value_or(0);
455 info.closeTimeResolution = duration{closeTimeResolution.value_or(0)};
462 soci::session& session,
467 s <<
"WHERE LedgerSeq = " << ledgerSeq;
475 s <<
"ORDER BY LedgerSeq DESC LIMIT 1";
481 soci::session& session,
487 " ORDER BY LedgerSeq ASC LIMIT 1";
493 soci::session& session,
499 " ORDER BY LedgerSeq DESC LIMIT 1";
505 soci::session& session,
510 s <<
"WHERE LedgerHash = '" << ledgerHash <<
"'";
520 "SELECT LedgerHash FROM Ledgers INDEXED BY SeqLedger WHERE LedgerSeq='";
521 sql.
append(beast::lexicalCastThrow<std::string>(ledgerIndex));
527 boost::optional<std::string> lh;
528 session << sql, soci::into(lh);
530 if (!session.got_data() || !lh)
546 soci::session& session,
551 boost::optional<std::string> lhO, phO;
553 session <<
"SELECT LedgerHash,PrevHash FROM Ledgers "
554 "INDEXED BY SeqLedger WHERE LedgerSeq = :ls;",
555 soci::into(lhO), soci::into(phO), soci::use(ledgerIndex);
559 auto stream = j.
trace();
560 JLOG(stream) <<
"Don't have ledger " << ledgerIndex;
564 LedgerHashPair hashes;
565 if (!hashes.ledgerHash.parseHex(*lhO) || !hashes.parentHash.parseHex(*phO))
567 auto stream = j.
trace();
568 JLOG(stream) <<
"Error parse hashes for ledger " << ledgerIndex;
577 soci::session& session,
583 "SELECT LedgerSeq,LedgerHash,PrevHash FROM Ledgers WHERE LedgerSeq >= ";
584 sql.
append(beast::lexicalCastThrow<std::string>(minSeq));
585 sql.
append(
" AND LedgerSeq <= ");
586 sql.
append(beast::lexicalCastThrow<std::string>(maxSeq));
592 boost::optional<std::string> ph;
594 (session.prepare << sql,
606 JLOG(j.
warn()) <<
"Error parsed hash for ledger seq: " << ls;
610 JLOG(j.
warn()) <<
"Null prev hash for ledger seq: " << ls;
614 JLOG(j.
warn()) <<
"Error parsed prev hash for ledger seq: " << ls;
622 soci::session& session,
630 "SELECT LedgerSeq, Status, RawTxn "
631 "FROM Transactions ORDER BY LedgerSeq DESC LIMIT %u,%u;") %
632 startIndex % quantity);
639 boost::optional<std::uint64_t> ledgerSeq;
640 boost::optional<std::string> status;
641 soci::blob sociRawTxnBlob(session);
646 (session.prepare << sql,
647 soci::into(ledgerSeq),
649 soci::into(sociRawTxnBlob, rti));
654 if (soci::i_ok == rti)
655 convert(sociRawTxnBlob, rawTxn);
660 ledgerSeq, status, rawTxn, app))
669 session <<
"SELECT COUNT(*) FROM Transactions;", soci::into(total);
702 RelationalDatabase::AccountTxOptions
const& options,
718 else if (options.limit == UINT32_MAX)
720 numberOfResults = binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH;
722 else if (!options.bUnlimited)
725 binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH, options.limit);
729 numberOfResults = options.limit;
734 if (numberOfResults <= *limit_used)
737 numberOfResults -= *limit_used;
743 if (options.maxLedger)
745 maxClause = boost::str(
746 boost::format(
"AND AccountTransactions.LedgerSeq <= '%u'") %
750 if (options.minLedger)
752 minClause = boost::str(
753 boost::format(
"AND AccountTransactions.LedgerSeq >= '%u'") %
761 boost::format(
"SELECT %s FROM AccountTransactions "
762 "WHERE Account = '%s' %s %s LIMIT %u, %u;") %
763 selection % app.accountIDCache().toBase58(options.account) %
764 maxClause % minClause %
765 beast::lexicalCastThrow<std::string>(options.offset) %
766 beast::lexicalCastThrow<std::string>(numberOfResults));
771 "AccountTransactions INNER JOIN Transactions "
772 "ON Transactions.TransID = AccountTransactions.TransID "
773 "WHERE Account = '%s' %s %s "
774 "ORDER BY AccountTransactions.LedgerSeq %s, "
775 "AccountTransactions.TxnSeq %s, AccountTransactions.TransID %s "
777 selection % app.accountIDCache().toBase58(options.account) %
778 maxClause % minClause % (descending ?
"DESC" :
"ASC") %
779 (descending ?
"DESC" :
"ASC") % (descending ?
"DESC" :
"ASC") %
780 beast::lexicalCastThrow<std::string>(options.offset) %
781 beast::lexicalCastThrow<std::string>(numberOfResults));
782 JLOG(j.
trace()) <<
"txSQL query: " << sql;
812 soci::session& session,
814 LedgerMaster& ledgerMaster,
815 RelationalDatabase::AccountTxOptions
const& options,
824 "AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta",
837 boost::optional<std::uint64_t> ledgerSeq;
838 boost::optional<std::string>
status;
839 soci::blob sociTxnBlob(session), sociTxnMetaBlob(session);
840 soci::indicator rti, tmi;
841 Blob rawTxn, txnMeta;
844 (session.prepare << sql,
845 soci::into(ledgerSeq),
847 soci::into(sociTxnBlob, rti),
848 soci::into(sociTxnMetaBlob, tmi));
853 if (soci::i_ok == rti)
858 if (soci::i_ok == tmi)
859 convert(sociTxnMetaBlob, txnMeta);
869 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0));
872 <<
"Recovering ledger " << seq <<
", txn " << txn->getID();
882 std::make_shared<TxMeta>(
883 txn->getID(), txn->getLedger(), txnMeta));
888 if (!total && limit_used)
890 RelationalDatabase::AccountTxOptions opt = options;
893 app,
"COUNT(*)", opt, limit_used, descending,
false,
false, j);
895 session << sql1, soci::into(total);
906 soci::session& session,
908 LedgerMaster& ledgerMaster,
909 RelationalDatabase::AccountTxOptions
const& options,
914 session, app, ledgerMaster, options, limit_used,
false, j);
919 soci::session& session,
927 session, app, ledgerMaster, options, limit_used,
true, j);
955 soci::session& session,
957 RelationalDatabase::AccountTxOptions
const& options,
966 "AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta",
980 boost::optional<std::uint64_t> ledgerSeq;
981 boost::optional<std::string>
status;
982 soci::blob sociTxnBlob(session), sociTxnMetaBlob(session);
983 soci::indicator rti, tmi;
986 (session.prepare << sql,
987 soci::into(ledgerSeq),
989 soci::into(sociTxnBlob, rti),
990 soci::into(sociTxnMetaBlob, tmi));
996 if (soci::i_ok == rti)
999 if (soci::i_ok == tmi)
1000 convert(sociTxnMetaBlob, txnMeta);
1003 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0));
1005 ret.
emplace_back(std::move(rawTxn), std::move(txnMeta), seq);
1009 if (!total && limit_used)
1011 RelationalDatabase::AccountTxOptions opt = options;
1014 app,
"COUNT(*)", opt, limit_used, descending,
true,
false, j);
1016 session << sql1, soci::into(total);
1022 return {ret, total};
1027 soci::session& session,
1029 RelationalDatabase::AccountTxOptions
const& options,
1033 return getAccountTxsB(session, app, options, limit_used,
false, j);
1038 soci::session& session,
1044 return getAccountTxsB(session, app, options, limit_used,
true, j);
1071 soci::session& session,
1072 AccountIDCache
const& idCache,
1077 RelationalDatabase::AccountTxPageOptions
const& options,
1084 bool lookingForMarker = options.marker.has_value();
1088 if (options.limit == 0 || options.limit == UINT32_MAX ||
1089 (options.limit > page_length && !options.bAdmin))
1090 numberOfResults = page_length;
1092 numberOfResults = options.limit;
1094 if (numberOfResults < limit_used)
1095 return {options.marker, -1};
1096 numberOfResults -= limit_used;
1106 if (lookingForMarker)
1108 findLedger = options.marker->ledgerSeq;
1109 findSeq = options.marker->txnSeq;
1114 newmarker = options.marker;
1117 R
"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
1118 Status,RawTxn,TxnMeta
1119 FROM AccountTransactions INNER JOIN Transactions
1120 ON Transactions.TransID = AccountTransactions.TransID
1121 AND AccountTransactions.Account = '%s' WHERE
1128 const char*
const order =
forward ?
"ASC" :
"DESC";
1130 if (findLedger == 0)
1134 prefix + (R
"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u'
1135 ORDER BY AccountTransactions.LedgerSeq %s,
1136 AccountTransactions.TxnSeq %s
1138 idCache.toBase58(options.account) % options.minLedger %
1139 options.maxLedger % order % order % queryLimit);
1145 forward ? findLedger + 1 : options.minLedger;
1147 forward ? options.maxLedger : findLedger - 1;
1149 auto b58acct = idCache.toBase58(options.account);
1152 R
"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
1153 Status,RawTxn,TxnMeta
1154 FROM AccountTransactions, Transactions WHERE
1155 (AccountTransactions.TransID = Transactions.TransID AND
1156 AccountTransactions.Account = '%s' AND
1157 AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u')
1159 (AccountTransactions.TransID = Transactions.TransID AND
1160 AccountTransactions.Account = '%s' AND
1161 AccountTransactions.LedgerSeq = '%u' AND
1162 AccountTransactions.TxnSeq %s '%u')
1163 ORDER BY AccountTransactions.LedgerSeq %s,
1164 AccountTransactions.TxnSeq %s
1167 b58acct % minLedger % maxLedger % b58acct % findLedger % compare %
1168 findSeq % order % order % queryLimit);
1176 boost::optional<std::uint64_t> ledgerSeq;
1177 boost::optional<std::uint32_t> txnSeq;
1178 boost::optional<std::string>
status;
1179 soci::blob txnData(session);
1180 soci::blob txnMeta(session);
1181 soci::indicator dataPresent, metaPresent;
1183 soci::statement st =
1184 (session.prepare << sql,
1185 soci::into(ledgerSeq),
1188 soci::into(txnData, dataPresent),
1189 soci::into(txnMeta, metaPresent));
1195 if (lookingForMarker)
1197 if (findLedger == ledgerSeq.value_or(0) &&
1198 findSeq == txnSeq.value_or(0))
1200 lookingForMarker =
false;
1205 else if (numberOfResults == 0)
1208 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0)),
1213 if (dataPresent == soci::i_ok)
1218 if (metaPresent == soci::i_ok)
1224 if (rawMeta.size() == 0)
1225 onUnsavedLedger(ledgerSeq.value_or(0));
1230 rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0)),
1233 std::move(rawMeta));
1247 return {newmarker, total};
1252 soci::session& session,
1275 soci::session& session,
1298 soci::session& session,
1305 "SELECT LedgerSeq,Status,RawTxn,TxnMeta "
1306 "FROM Transactions WHERE TransID='";
1312 boost::optional<std::uint64_t> ledgerSeq;
1313 boost::optional<std::string> status;
1314 Blob rawTxn, rawMeta;
1316 soci::blob sociRawTxnBlob(session), sociRawMetaBlob(session);
1317 soci::indicator txn, meta;
1319 session << sql, soci::into(ledgerSeq), soci::into(status),
1320 soci::into(sociRawTxnBlob, txn), soci::into(sociRawMetaBlob, meta);
1322 auto const got_data = session.got_data();
1324 if ((!got_data || txn != soci::i_ok || meta != soci::i_ok) && !
range)
1330 soci::indicator rti;
1333 <<
"SELECT COUNT(DISTINCT LedgerSeq) FROM Transactions WHERE "
1334 "LedgerSeq BETWEEN "
1335 <<
range->first() <<
" AND " <<
range->last() <<
";",
1336 soci::into(count, rti);
1338 if (!session.got_data() || rti != soci::i_ok)
1341 return count == (
range->last() -
range->first() + 1)
1346 convert(sociRawTxnBlob, rawTxn);
1347 convert(sociRawMetaBlob, rawMeta);
1356 return std::pair{std::move(txn),
nullptr};
1359 rangeCheckedCast<std::uint32_t>(ledgerSeq.value());
1361 auto txMeta = std::make_shared<TxMeta>(
id, inLedger, rawMeta);
1363 return std::pair{std::move(txn), std::move(txMeta)};
1368 <<
"Unable to deserialize transaction from raw SQL value. Error: "
1380 boost::filesystem::space_info
space =
1381 boost::filesystem::space(config.legacy(
"database_path"));
1385 JLOG(j.
fatal()) <<
"Remaining free disk space is less than 512MB";
1389 if (config.useTxTables())
1392 boost::filesystem::path dbPath = dbSetup.dataDir /
TxDBName;
1393 boost::system::error_code ec;
1395 boost::filesystem::file_size(dbPath, ec);
1399 <<
"Error checking transaction db file size: " << ec.message();
1403 static auto const pageSize = [&] {
1405 session <<
"PRAGMA page_size;", soci::into(ps);
1408 static auto const maxPages = [&] {
1410 session <<
"PRAGMA max_page_count;", soci::into(mp);
1414 session <<
"PRAGMA page_count;", soci::into(pageCount);
1417 safe_cast<std::uint64_t>(freePages) * pageSize;
1419 <<
"Transaction DB pathname: " << dbPath.string()
1420 <<
"; file size: " << dbSize.
value_or(-1) <<
" bytes"
1421 <<
"; SQLite page size: " << pageSize <<
" bytes"
1422 <<
"; Free pages: " << freePages <<
"; Free space: " << freeSpace
1424 <<
"Note that this does not take into account available disk "
1430 <<
"Free SQLite space for transaction db is less than "
1431 "512MB. To fix this, rippled must be executed with the "
1432 "vacuum parameter before restarting. "
1433 "Note that this activity can take multiple days, "
1434 "depending on database size.";