20 #include <ripple/app/ledger/InboundLedgers.h>
21 #include <ripple/app/ledger/LedgerMaster.h>
22 #include <ripple/app/misc/NetworkOPs.h>
23 #include <ripple/basics/ByteUtilities.h>
24 #include <ripple/basics/chrono.h>
25 #include <ripple/basics/random.h>
26 #include <ripple/core/ConfigSections.h>
27 #include <ripple/nodestore/DummyScheduler.h>
28 #include <ripple/nodestore/impl/DatabaseShardImp.h>
29 #include <ripple/overlay/Overlay.h>
30 #include <ripple/overlay/predicates.h>
31 #include <ripple/protocol/HashPrefix.h>
33 #include <boost/algorithm/string/predicate.hpp>
56 , avgShardFileSz_(ledgersPerShard_ *
kilobytes(192))
72 JLOG(
j_.
error()) <<
"already initialized";
78 JLOG(
j_.
error()) <<
"invalid configuration file settings";
84 using namespace boost::filesystem;
87 if (!is_directory(
dir_))
89 JLOG(
j_.
error()) <<
"'path' must be a directory";
94 create_directories(
dir_);
96 ctx_ = std::make_unique<nudb::context>();
100 for (
auto const& d : directory_iterator(
dir_))
102 if (!is_directory(d))
106 auto dirName = d.path().stem().string();
107 if (!
std::all_of(dirName.begin(), dirName.end(), [](
auto c) {
108 return ::isdigit(static_cast<unsigned char>(c));
117 JLOG(
j_.
error()) <<
"shard " << shardIndex
118 <<
" comes before earliest shard index "
128 JLOG(
j_.
warn()) <<
"shard " << shardIndex
129 <<
" previously failed import, removing";
130 remove_all(shardDir);
135 std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
138 if (!shard->isLegacy())
142 shard->removeOnDestroy();
144 <<
"shard " << shardIndex <<
" removed, legacy shard";
148 if (shard->isFinal())
154 else if (shard->isBackendComplete())
156 auto const result{
shards_.emplace(
160 result.first->second,
true, lock, boost::none);
167 <<
"more than one shard being acquired";
181 <<
"exception " << e.
what() <<
" in function " << __func__;
193 boost::optional<std::uint32_t>
196 boost::optional<std::uint32_t> shardIndex;
205 return it->second.shard->prepare();
216 JLOG(
j_.
debug()) <<
"maximum storage size reached";
222 JLOG(
j_.
error()) <<
"insufficient storage space available";
232 JLOG(
j_.
debug()) <<
"no new shards to add";
240 auto shard{std::make_unique<Shard>(
app_, *
this, *shardIndex,
j_)};
244 auto const seq{shard->prepare()};
259 JLOG(j.error()) <<
"shard " << shardIndex <<
" " << msg;
266 return fail(
"cannot be stored at this time");
271 "comes before earliest shard index " +
280 return fail(
"has an invalid index");
291 JLOG(
j_.
debug()) <<
"shard " << shardIndex
292 <<
" is already stored or queued for import";
298 return fail(
"maximum storage size reached");
300 return fail(
"insufficient storage space available");
312 if (
auto const it{
shards_.find(shardIndex)};
341 boost::filesystem::path
const& srcDir)
343 using namespace boost::filesystem;
346 if (!is_directory(srcDir) || is_empty(srcDir))
348 JLOG(
j_.
error()) <<
"invalid source directory " << srcDir.string();
354 JLOG(
j_.
error()) <<
"exception " << e.
what() <<
" in function "
364 JLOG(
j_.
error()) <<
"shard " << shardIndex
365 <<
" expected hash not found";
369 auto renameDir = [&](path
const& src, path
const& dst) {
377 <<
"exception " << e.
what() <<
" in function " << __func__;
389 if (
auto const it{
shards_.find(shardIndex)}; it ==
shards_.end() ||
392 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
400 if (!renameDir(srcDir, dstDir))
404 auto shard{std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
405 if (!shard->open(
scheduler_, *
ctx_) || !shard->isBackendComplete())
407 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
409 renameDir(dstDir, srcDir);
415 auto const it{
shards_.find(shardIndex)};
416 if (it ==
shards_.end() || it->second.shard ||
419 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
421 renameDir(dstDir, srcDir);
425 it->second.shard = std::move(shard);
445 shard = it->second.shard;
446 state = it->second.state;
459 if (shard->containsLedger(seq))
467 auto nObj{
fetch(hash, seq)};
476 auto ledger{std::make_shared<Ledger>(
481 if (ledger->info().seq != seq)
486 if (ledger->info().hash != hash)
489 "encountered invalid ledger hash " +
to_string(hash) +
494 if (!ledger->stateMap().fetchRoot(
495 SHAMapHash{ledger->info().accountHash},
nullptr))
498 "is missing root STATE node on hash " +
to_string(hash) +
502 if (ledger->info().txHash.isNonZero())
504 if (!ledger->txMap().fetchRoot(
508 "is missing root TXN node on hash " +
to_string(hash) +
518 if (ledger->info().hash.isZero())
520 JLOG(
j_.
error()) <<
"zero ledger hash for ledger sequence "
521 << ledger->info().seq;
524 if (ledger->info().accountHash.isZero())
526 JLOG(
j_.
error()) <<
"zero account hash for ledger sequence "
527 << ledger->info().seq;
530 if (ledger->stateMap().getHash().isNonZero() &&
531 !ledger->stateMap().isValid())
533 JLOG(
j_.
error()) <<
"invalid state map for ledger sequence "
534 << ledger->info().seq;
537 if (ledger->info().txHash.isNonZero() && !ledger->txMap().isValid())
539 JLOG(
j_.
error()) <<
"invalid transaction map for ledger sequence "
540 << ledger->info().seq;
553 <<
"shard " << shardIndex <<
" is not being acquired";
558 shard = it->second.shard;
562 <<
"shard " << shardIndex <<
" is not being acquired";
598 for (
auto const& e : shards)
600 if (
auto shard{e.lock()}; shard)
601 shard->finalize(
true, boost::none);
621 e.second.shard->stop();
637 JLOG(
j_.
error()) <<
"invalid source database";
644 auto loadLedger = [&](
bool ascendSort =
645 true) -> boost::optional<std::uint32_t> {
649 "WHERE LedgerSeq >= " +
651 " order by LedgerSeq " + (ascendSort ?
"asc" :
"desc") +
655 if (!ledger || seq == 0)
657 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
658 " the SQLite database to import";
665 auto seq{loadLedger()};
675 seq = loadLedger(
false);
684 if (latestIndex < earliestIndex)
686 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
687 " the SQLite database to import";
694 shardIndex <= latestIndex;
699 JLOG(
j_.
error()) <<
"maximum storage size reached";
705 JLOG(
j_.
error()) <<
"insufficient storage space available";
714 JLOG(
j_.
debug()) <<
"shard " << shardIndex <<
" already exists";
723 auto const numLedgers{
727 if (ledgerHashes.size() != numLedgers)
733 if (!source.
fetch(ledgerHashes[n].first, n))
735 JLOG(
j_.
warn()) <<
"SQLite ledger sequence " << n
736 <<
" mismatches node store";
747 auto shard{std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
758 JLOG(
j_.
error()) <<
"shard " << shardIndex
759 <<
" failed to create temp marker file";
760 shard->removeOnDestroy();
768 boost::optional<uint256> lastLedgerHash;
770 while (
auto seq = shard->prepare())
773 if (!ledger || ledger->info().seq != seq)
786 if (!shard->store(ledger))
790 lastLedgerHash = ledger->info().hash;
792 recentStored = ledger;
795 using namespace boost::filesystem;
796 if (lastLedgerHash && shard->isBackendComplete())
809 shard->getBackend()->store(nObj);
813 remove_all(markerFile);
815 JLOG(
j_.
debug()) <<
"shard " << shardIndex
816 <<
" was successfully imported";
818 auto const result{
shards_.emplace(
822 result.first->second,
true, lock, boost::none);
827 <<
" in function " << __func__;
828 shard->removeOnDestroy();
834 <<
"shard " << shardIndex <<
" failed to import";
835 shard->removeOnDestroy();
854 shard = it->second.shard;
859 return shard->getBackend()->getWriteLoad();
878 <<
"shard " << shardIndex <<
" is not being acquired";
883 shard = it->second.shard;
887 <<
"shard " << shardIndex <<
" is not being acquired";
892 auto [backend, pCache, nCache] = shard->getBackendAll();
895 pCache->canonicalize_replace_cache(hash, nObj);
896 backend->store(nObj);
907 return doFetch(hash, seq, *cache.first, *cache.second,
false);
921 object = cache.first->fetch(hash);
922 if (
object || cache.second->touch_if_exists(hash))
933 auto const seq{srcLedger->info().seq};
943 <<
"shard " << shardIndex <<
" is not being acquired";
948 shard = it->second.shard;
952 <<
"shard " << shardIndex <<
" is not being acquired";
957 if (shard->containsLedger(seq))
959 JLOG(
j_.
trace()) <<
"shard " << shardIndex <<
" ledger already stored";
964 auto [backend, pCache, nCache] = shard->getBackendAll();
966 *srcLedger, backend, pCache, nCache,
nullptr))
984 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end() &&
988 shard = it->second.shard;
1006 shard = it->second.shard;
1011 return shard->pCache()->getHitRate();
1030 for (
auto const& e : shards)
1032 if (
auto shard{e.lock()}; shard)
1057 get_if_exists<std::uint32_t>(
1058 section,
"earliest_seq", shardDBEarliestSeq);
1061 get_if_exists<std::uint32_t>(
1066 if (shardDBEarliestSeq != nodeDBEarliestSeq)
1070 "] define different 'earliest_seq' values");
1074 using namespace boost::filesystem;
1075 if (!get_if_exists<path>(section,
"path",
dir_))
1076 return fail(
"'path' missing");
1080 if (!get_if_exists<std::uint64_t>(section,
"max_size_gb", sz))
1081 return fail(
"'max_size_gb' missing");
1083 if ((sz << 30) < sz)
1084 return fail(
"'max_size_gb' overflow");
1088 return fail(
"'max_size_gb' must be at least 10");
1094 if (section.exists(
"ledgers_per_shard"))
1097 if (!config.standalone())
1098 return fail(
"'ledgers_per_shard' only honored in stand alone");
1102 return fail(
"'ledgers_per_shard' must be a multiple of 256");
1106 backendName_ = get<std::string>(section,
"type",
"nudb");
1108 return fail(
"'type' value unsupported");
1122 if (
auto const it{
shards_.find(shardIndex)};
1123 it !=
shards_.end() && it->second.shard)
1125 shard = it->second.shard;
1134 boost::optional<std::uint32_t>
1142 auto const maxShardIndex{[
this, validLedgerSeq]() {
1151 if (
shards_.size() >= maxNumShards)
1154 if (maxShardIndex < 1024 ||
1155 static_cast<float>(
shards_.size()) / maxNumShards > 0.5f)
1163 shardIndex <= maxShardIndex;
1183 for (
int i = 0; i < 40; ++i)
1199 boost::optional<uint256>
const& expectedHash)
1201 assert(shardInfo.
shard);
1203 assert(shardInfo.
shard->isBackendComplete());
1206 auto const shardIndex{shardInfo.
shard->index()};
1209 taskQueue_->addTask([
this, shardIndex, writeSQLite, expectedHash]() {
1216 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end())
1218 shard = it->second.shard;
1222 JLOG(
j_.
error()) <<
"Unable to finalize shard " << shardIndex;
1227 if (!shard->finalize(writeSQLite, expectedHash))
1242 auto const it{
shards_.find(shardIndex)};
1255 protocol::TMPeerShardInfo message;
1257 message.set_nodepubkey(publicKey.data(), publicKey.size());
1260 message, protocol::mtPEER_SHARD_INFO)));
1284 for (
auto const& e : shards)
1286 if (
auto shard{e.lock()}; shard)
1288 auto [sz, fd] = shard->fileInfo();
1302 JLOG(
j_.
warn()) <<
"maximum storage size reached";
1308 <<
"maximum shard store size exceeds available storage space";
1320 rs.insert(e.second.shard->index());
1336 if (
auto const it{
shards_.find(shardIndex)};
1337 it !=
shards_.end() && it->second.shard)
1339 shard = it->second.shard;
1347 std::tie(std::ignore, pCache, nCache) = shard->getBackendAll();
1357 return boost::filesystem::space(
dir_).available;
1361 JLOG(
j_.
error()) <<
"exception " << e.
what() <<
" in function "
1374 if (!shard->store(ledger))
1380 else if (shard->isBackendComplete())
1384 if (
auto const it{
shards_.find(shard->index())}; it !=
shards_.end())
1395 <<
"shard " << shard->index() <<
" is no longer being acquired";
1412 if ((
shards_.erase(shard->index()) > 0) && shard->isFinal())
1416 shard->removeOnDestroy();
1434 if (section.empty())
1437 return std::make_unique<DatabaseShardImp>(
1438 app, parent,
"ShardStore", scheduler, readThreads, j);