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";
746 auto shard{std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
757 JLOG(
j_.
error()) <<
"shard " << shardIndex
758 <<
" failed to create temp marker file";
759 shard->removeOnDestroy();
767 boost::optional<uint256> lastLedgerHash;
769 while (
auto seq = shard->prepare())
772 if (!ledger || ledger->info().seq != seq)
785 if (!shard->store(ledger))
789 lastLedgerHash = ledger->info().hash;
791 recentStored = ledger;
794 using namespace boost::filesystem;
795 if (lastLedgerHash && shard->isBackendComplete())
808 shard->getBackend()->store(nObj);
812 remove_all(markerFile);
814 JLOG(
j_.
debug()) <<
"shard " << shardIndex
815 <<
" was successfully imported";
817 auto const result{
shards_.emplace(
821 result.first->second,
true, lock, boost::none);
826 <<
" in function " << __func__;
827 shard->removeOnDestroy();
833 <<
"shard " << shardIndex <<
" failed to import";
834 shard->removeOnDestroy();
853 shard = it->second.shard;
858 return shard->getBackend()->getWriteLoad();
877 <<
"shard " << shardIndex <<
" is not being acquired";
882 shard = it->second.shard;
886 <<
"shard " << shardIndex <<
" is not being acquired";
891 auto [backend, pCache, nCache] = shard->getBackendAll();
894 pCache->canonicalize_replace_cache(hash, nObj);
895 backend->store(nObj);
906 return doFetch(hash, seq, *cache.first, *cache.second,
false);
920 object = cache.first->fetch(hash);
921 if (
object || cache.second->touch_if_exists(hash))
932 auto const seq{srcLedger->info().seq};
942 <<
"shard " << shardIndex <<
" is not being acquired";
947 shard = it->second.shard;
951 <<
"shard " << shardIndex <<
" is not being acquired";
956 if (shard->containsLedger(seq))
958 JLOG(
j_.
trace()) <<
"shard " << shardIndex <<
" ledger already stored";
963 auto [backend, pCache, nCache] = shard->getBackendAll();
965 *srcLedger, backend, pCache, nCache,
nullptr))
983 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end() &&
987 shard = it->second.shard;
1005 shard = it->second.shard;
1010 return shard->pCache()->getHitRate();
1029 for (
auto const& e : shards)
1031 if (
auto shard{e.lock()}; shard)
1056 get_if_exists<std::uint32_t>(
1057 section,
"earliest_seq", shardDBEarliestSeq);
1060 get_if_exists<std::uint32_t>(
1065 if (shardDBEarliestSeq != nodeDBEarliestSeq)
1069 "] define different 'earliest_seq' values");
1073 using namespace boost::filesystem;
1074 if (!get_if_exists<path>(section,
"path",
dir_))
1075 return fail(
"'path' missing");
1079 if (!get_if_exists<std::uint64_t>(section,
"max_size_gb", sz))
1080 return fail(
"'max_size_gb' missing");
1082 if ((sz << 30) < sz)
1083 return fail(
"'max_size_gb' overflow");
1087 return fail(
"'max_size_gb' must be at least 10");
1093 if (section.exists(
"ledgers_per_shard"))
1096 if (!config.standalone())
1097 return fail(
"'ledgers_per_shard' only honored in stand alone");
1101 return fail(
"'ledgers_per_shard' must be a multiple of 256");
1108 backendName_ = get<std::string>(section,
"type",
"nudb");
1110 return fail(
"'type' value unsupported");
1124 if (
auto const it{
shards_.find(shardIndex)};
1125 it !=
shards_.end() && it->second.shard)
1127 shard = it->second.shard;
1136 boost::optional<std::uint32_t>
1144 auto const maxShardIndex{[
this, validLedgerSeq]() {
1153 if (
shards_.size() >= maxNumShards)
1156 if (maxShardIndex < 1024 ||
1157 static_cast<float>(
shards_.size()) / maxNumShards > 0.5f)
1165 shardIndex <= maxShardIndex;
1185 for (
int i = 0; i < 40; ++i)
1201 boost::optional<uint256>
const& expectedHash)
1203 assert(shardInfo.
shard);
1205 assert(shardInfo.
shard->isBackendComplete());
1208 auto const shardIndex{shardInfo.
shard->index()};
1211 taskQueue_->addTask([
this, shardIndex, writeSQLite, expectedHash]() {
1218 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end())
1220 shard = it->second.shard;
1224 JLOG(
j_.
error()) <<
"Unable to finalize shard " << shardIndex;
1229 if (!shard->finalize(writeSQLite, expectedHash))
1244 auto const it{
shards_.find(shardIndex)};
1257 protocol::TMPeerShardInfo message;
1259 message.set_nodepubkey(publicKey.data(), publicKey.size());
1262 message, protocol::mtPEER_SHARD_INFO)));
1286 for (
auto const& e : shards)
1288 if (
auto shard{e.lock()}; shard)
1290 auto [sz, fd] = shard->fileInfo();
1304 JLOG(
j_.
warn()) <<
"maximum storage size reached";
1310 <<
"maximum shard store size exceeds available storage space";
1322 rs.insert(e.second.shard->index());
1338 if (
auto const it{
shards_.find(shardIndex)};
1339 it !=
shards_.end() && it->second.shard)
1341 shard = it->second.shard;
1349 std::tie(std::ignore, pCache, nCache) = shard->getBackendAll();
1359 return boost::filesystem::space(
dir_).available;
1363 JLOG(
j_.
error()) <<
"exception " << e.
what() <<
" in function "
1376 if (!shard->store(ledger))
1382 else if (shard->isBackendComplete())
1386 if (
auto const it{
shards_.find(shard->index())}; it !=
shards_.end())
1397 <<
"shard " << shard->index() <<
" is no longer being acquired";
1414 if ((
shards_.erase(shard->index()) > 0) && shard->isFinal())
1418 shard->removeOnDestroy();
1436 if (section.empty())
1439 return std::make_unique<DatabaseShardImp>(
1440 app, parent,
"ShardStore", scheduler, readThreads, j);