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_)};
139 shard->removeOnDestroy();
141 <<
"shard " << shardIndex <<
" removed, "
142 << (shard->isLegacy() ?
"legacy" :
"corrupted")
147 if (shard->isFinal())
153 else if (shard->isBackendComplete())
155 auto const result{
shards_.emplace(
159 result.first->second,
true, lock, boost::none);
166 <<
"more than one shard being acquired";
180 <<
"exception " << e.
what() <<
" in function " << __func__;
192 boost::optional<std::uint32_t>
195 boost::optional<std::uint32_t> shardIndex;
204 return it->second.shard->prepare();
215 JLOG(
j_.
debug()) <<
"maximum storage size reached";
221 JLOG(
j_.
error()) <<
"insufficient storage space available";
231 JLOG(
j_.
debug()) <<
"no new shards to add";
239 auto shard{std::make_unique<Shard>(
app_, *
this, *shardIndex,
j_)};
243 auto const seq{shard->prepare()};
258 JLOG(j.error()) <<
"shard " << shardIndex <<
" " << msg;
265 return fail(
"cannot be stored at this time");
270 "comes before earliest shard index " +
279 return fail(
"has an invalid index");
290 JLOG(
j_.
debug()) <<
"shard " << shardIndex
291 <<
" is already stored or queued for import";
297 return fail(
"maximum storage size reached");
299 return fail(
"insufficient storage space available");
311 if (
auto const it{
shards_.find(shardIndex)};
340 boost::filesystem::path
const& srcDir)
342 using namespace boost::filesystem;
345 if (!is_directory(srcDir) || is_empty(srcDir))
347 JLOG(
j_.
error()) <<
"invalid source directory " << srcDir.string();
353 JLOG(
j_.
error()) <<
"exception " << e.
what() <<
" in function "
363 JLOG(
j_.
error()) <<
"shard " << shardIndex
364 <<
" expected hash not found";
368 auto renameDir = [&](path
const& src, path
const& dst) {
376 <<
"exception " << e.
what() <<
" in function " << __func__;
388 if (
auto const it{
shards_.find(shardIndex)}; it ==
shards_.end() ||
391 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
399 if (!renameDir(srcDir, dstDir))
403 auto shard{std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
404 if (!shard->open(
scheduler_, *
ctx_) || !shard->isBackendComplete())
406 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
408 renameDir(dstDir, srcDir);
414 auto const it{
shards_.find(shardIndex)};
415 if (it ==
shards_.end() || it->second.shard ||
418 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
420 renameDir(dstDir, srcDir);
424 it->second.shard = std::move(shard);
444 shard = it->second.shard;
445 state = it->second.state;
458 if (shard->containsLedger(seq))
466 auto nObj{
fetch(hash, seq)};
475 auto ledger{std::make_shared<Ledger>(
480 if (ledger->info().seq != seq)
485 if (ledger->info().hash != hash)
488 "encountered invalid ledger hash " +
to_string(hash) +
493 if (!ledger->stateMap().fetchRoot(
494 SHAMapHash{ledger->info().accountHash},
nullptr))
497 "is missing root STATE node on hash " +
to_string(hash) +
501 if (ledger->info().txHash.isNonZero())
503 if (!ledger->txMap().fetchRoot(
507 "is missing root TXN node on hash " +
to_string(hash) +
517 if (ledger->info().hash.isZero())
519 JLOG(
j_.
error()) <<
"zero ledger hash for ledger sequence "
520 << ledger->info().seq;
523 if (ledger->info().accountHash.isZero())
525 JLOG(
j_.
error()) <<
"zero account hash for ledger sequence "
526 << ledger->info().seq;
529 if (ledger->stateMap().getHash().isNonZero() &&
530 !ledger->stateMap().isValid())
532 JLOG(
j_.
error()) <<
"invalid state map for ledger sequence "
533 << ledger->info().seq;
536 if (ledger->info().txHash.isNonZero() && !ledger->txMap().isValid())
538 JLOG(
j_.
error()) <<
"invalid transaction map for ledger sequence "
539 << ledger->info().seq;
552 <<
"shard " << shardIndex <<
" is not being acquired";
557 shard = it->second.shard;
561 <<
"shard " << shardIndex <<
" is not being acquired";
597 for (
auto const& e : shards)
599 if (
auto shard{e.lock()}; shard)
600 shard->finalize(
true, boost::none);
620 e.second.shard->stop();
636 JLOG(
j_.
error()) <<
"invalid source database";
643 auto loadLedger = [&](
bool ascendSort =
644 true) -> boost::optional<std::uint32_t> {
648 "WHERE LedgerSeq >= " +
650 " order by LedgerSeq " + (ascendSort ?
"asc" :
"desc") +
654 if (!ledger || seq == 0)
656 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
657 " the SQLite database to import";
664 auto seq{loadLedger()};
674 seq = loadLedger(
false);
683 if (latestIndex < earliestIndex)
685 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
686 " the SQLite database to import";
693 shardIndex <= latestIndex;
698 JLOG(
j_.
error()) <<
"maximum storage size reached";
704 JLOG(
j_.
error()) <<
"insufficient storage space available";
713 JLOG(
j_.
debug()) <<
"shard " << shardIndex <<
" already exists";
722 auto const numLedgers{
726 if (ledgerHashes.size() != numLedgers)
732 if (!source.
fetch(ledgerHashes[n].first, n))
734 JLOG(
j_.
warn()) <<
"SQLite ledger sequence " << n
735 <<
" mismatches node store";
745 auto shard{std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
756 JLOG(
j_.
error()) <<
"shard " << shardIndex
757 <<
" failed to create temp marker file";
758 shard->removeOnDestroy();
766 boost::optional<uint256> lastLedgerHash;
768 while (
auto seq = shard->prepare())
771 if (!ledger || ledger->info().seq != seq)
784 if (!shard->store(ledger))
788 lastLedgerHash = ledger->info().hash;
790 recentStored = ledger;
793 using namespace boost::filesystem;
794 if (lastLedgerHash && shard->isBackendComplete())
807 shard->getBackend()->store(nObj);
811 remove_all(markerFile);
813 JLOG(
j_.
debug()) <<
"shard " << shardIndex
814 <<
" was successfully imported";
816 auto const result{
shards_.emplace(
820 result.first->second,
true, lock, boost::none);
825 <<
" in function " << __func__;
826 shard->removeOnDestroy();
832 <<
"shard " << shardIndex <<
" failed to import";
833 shard->removeOnDestroy();
852 shard = it->second.shard;
857 return shard->getBackend()->getWriteLoad();
876 <<
"shard " << shardIndex <<
" is not being acquired";
881 shard = it->second.shard;
885 <<
"shard " << shardIndex <<
" is not being acquired";
890 auto [backend, pCache, nCache] = shard->getBackendAll();
893 pCache->canonicalize_replace_cache(hash, nObj);
894 backend->store(nObj);
905 return doFetch(hash, seq, *cache.first, *cache.second,
false);
919 object = cache.first->fetch(hash);
920 if (
object || cache.second->touch_if_exists(hash))
931 auto const seq{srcLedger->info().seq};
941 <<
"shard " << shardIndex <<
" is not being acquired";
946 shard = it->second.shard;
950 <<
"shard " << shardIndex <<
" is not being acquired";
955 if (shard->containsLedger(seq))
957 JLOG(
j_.
trace()) <<
"shard " << shardIndex <<
" ledger already stored";
962 auto [backend, pCache, nCache] = shard->getBackendAll();
964 *srcLedger, backend, pCache, nCache,
nullptr))
982 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end() &&
986 shard = it->second.shard;
1004 shard = it->second.shard;
1009 return shard->pCache()->getHitRate();
1028 for (
auto const& e : shards)
1030 if (
auto shard{e.lock()}; shard)
1055 get_if_exists<std::uint32_t>(
1056 section,
"earliest_seq", shardDBEarliestSeq);
1059 get_if_exists<std::uint32_t>(
1064 if (shardDBEarliestSeq != nodeDBEarliestSeq)
1068 "] define different 'earliest_seq' values");
1072 using namespace boost::filesystem;
1073 if (!get_if_exists<path>(section,
"path",
dir_))
1074 return fail(
"'path' missing");
1078 if (!get_if_exists<std::uint64_t>(section,
"max_size_gb", sz))
1079 return fail(
"'max_size_gb' missing");
1081 if ((sz << 30) < sz)
1082 return fail(
"'max_size_gb' overflow");
1086 return fail(
"'max_size_gb' must be at least 10");
1092 if (section.exists(
"ledgers_per_shard"))
1095 if (!config.standalone())
1096 return fail(
"'ledgers_per_shard' only honored in stand alone");
1100 return fail(
"'ledgers_per_shard' must be a multiple of 256");
1107 backendName_ = get<std::string>(section,
"type",
"nudb");
1109 return fail(
"'type' value unsupported");
1123 if (
auto const it{
shards_.find(shardIndex)};
1124 it !=
shards_.end() && it->second.shard)
1126 shard = it->second.shard;
1135 boost::optional<std::uint32_t>
1143 auto const maxShardIndex{[
this, validLedgerSeq]() {
1152 if (
shards_.size() >= maxNumShards)
1155 if (maxShardIndex < 1024 ||
1156 static_cast<float>(
shards_.size()) / maxNumShards > 0.5f)
1164 shardIndex <= maxShardIndex;
1184 for (
int i = 0; i < 40; ++i)
1200 boost::optional<uint256>
const& expectedHash)
1202 assert(shardInfo.
shard);
1204 assert(shardInfo.
shard->isBackendComplete());
1207 auto const shardIndex{shardInfo.
shard->index()};
1210 taskQueue_->addTask([
this, shardIndex, writeSQLite, expectedHash]() {
1217 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end())
1219 shard = it->second.shard;
1223 JLOG(
j_.
error()) <<
"Unable to finalize shard " << shardIndex;
1228 if (!shard->finalize(writeSQLite, expectedHash))
1243 auto const it{
shards_.find(shardIndex)};
1256 protocol::TMPeerShardInfo message;
1258 message.set_nodepubkey(publicKey.data(), publicKey.size());
1261 message, protocol::mtPEER_SHARD_INFO)));
1285 for (
auto const& e : shards)
1287 if (
auto shard{e.lock()}; shard)
1289 auto [sz, fd] = shard->fileInfo();
1303 JLOG(
j_.
warn()) <<
"maximum storage size reached";
1309 <<
"maximum shard store size exceeds available storage space";
1321 rs.insert(e.second.shard->index());
1337 if (
auto const it{
shards_.find(shardIndex)};
1338 it !=
shards_.end() && it->second.shard)
1340 shard = it->second.shard;
1348 std::tie(std::ignore, pCache, nCache) = shard->getBackendAll();
1358 return boost::filesystem::space(
dir_).available;
1362 JLOG(
j_.
error()) <<
"exception " << e.
what() <<
" in function "
1375 if (!shard->store(ledger))
1381 else if (shard->isBackendComplete())
1385 if (
auto const it{
shards_.find(shard->index())}; it !=
shards_.end())
1396 <<
"shard " << shard->index() <<
" is no longer being acquired";
1413 if ((
shards_.erase(shard->index()) > 0) && shard->isFinal())
1417 shard->removeOnDestroy();
1435 if (section.empty())
1438 return std::make_unique<DatabaseShardImp>(
1439 app, parent,
"ShardStore", scheduler, readThreads, j);