mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
603 lines
16 KiB
C++
603 lines
16 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012-2014 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <ripple/app/misc/NetworkOPs.h>
|
|
#include <ripple/basics/Archive.h>
|
|
#include <ripple/basics/BasicConfig.h>
|
|
#include <ripple/core/ConfigSections.h>
|
|
#include <ripple/nodestore/DatabaseShard.h>
|
|
#include <ripple/rpc/ShardArchiveHandler.h>
|
|
#include <ripple/rpc/impl/Handler.h>
|
|
|
|
#include <memory>
|
|
|
|
namespace ripple {
|
|
namespace RPC {
|
|
|
|
using namespace boost::filesystem;
|
|
using namespace std::chrono_literals;
|
|
|
|
boost::filesystem::path
|
|
ShardArchiveHandler::getDownloadDirectory(Config const& config)
|
|
{
|
|
return get(config.section(ConfigSection::shardDatabase()),
|
|
"download_path",
|
|
get(config.section(ConfigSection::shardDatabase()),
|
|
"path",
|
|
"")) /
|
|
"download";
|
|
}
|
|
|
|
std::unique_ptr<ShardArchiveHandler>
|
|
ShardArchiveHandler::makeShardArchiveHandler(
|
|
Application& app,
|
|
Stoppable& parent)
|
|
{
|
|
return std::make_unique<ShardArchiveHandler>(app, parent);
|
|
}
|
|
|
|
std::unique_ptr<ShardArchiveHandler>
|
|
ShardArchiveHandler::tryMakeRecoveryHandler(Application& app, Stoppable& parent)
|
|
{
|
|
auto const downloadDir(getDownloadDirectory(app.config()));
|
|
|
|
// Create the handler iff the database
|
|
// is present.
|
|
if (exists(downloadDir / stateDBName) &&
|
|
is_regular_file(downloadDir / stateDBName))
|
|
{
|
|
return std::make_unique<RecoveryHandler>(app, parent);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ShardArchiveHandler::ShardArchiveHandler(Application& app, Stoppable& parent)
|
|
: Stoppable("ShardArchiveHandler", parent)
|
|
, process_(false)
|
|
, app_(app)
|
|
, j_(app.journal("ShardArchiveHandler"))
|
|
, downloadDir_(getDownloadDirectory(app.config()))
|
|
, timer_(app_.getIOService())
|
|
, verificationScheduler_(
|
|
std::chrono::seconds(get<std::uint32_t>(
|
|
app.config().section(ConfigSection::shardDatabase()),
|
|
"shard_verification_retry_interval")),
|
|
|
|
get<std::uint32_t>(
|
|
app.config().section(ConfigSection::shardDatabase()),
|
|
"shard_verification_max_attempts"))
|
|
{
|
|
assert(app_.getShardStore());
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::init()
|
|
{
|
|
std::lock_guard lock(m_);
|
|
|
|
if (process_ || downloader_ != nullptr || sqliteDB_ != nullptr)
|
|
{
|
|
JLOG(j_.warn()) << "Archives already being processed";
|
|
return false;
|
|
}
|
|
|
|
// Initialize from pre-existing database
|
|
if (exists(downloadDir_ / stateDBName) &&
|
|
is_regular_file(downloadDir_ / stateDBName))
|
|
{
|
|
downloader_.reset(
|
|
new DatabaseDownloader(app_.getIOService(), j_, app_.config()));
|
|
|
|
return initFromDB(lock);
|
|
}
|
|
|
|
// Fresh initialization
|
|
else
|
|
{
|
|
try
|
|
{
|
|
create_directories(downloadDir_);
|
|
|
|
sqliteDB_ = std::make_unique<DatabaseCon>(
|
|
downloadDir_,
|
|
stateDBName,
|
|
DownloaderDBPragma,
|
|
ShardArchiveHandlerDBInit);
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error())
|
|
<< "exception: " << e.what() << " in function: " << __func__;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::initFromDB(std::lock_guard<std::mutex> const& lock)
|
|
{
|
|
try
|
|
{
|
|
using namespace boost::filesystem;
|
|
|
|
assert(
|
|
exists(downloadDir_ / stateDBName) &&
|
|
is_regular_file(downloadDir_ / stateDBName));
|
|
|
|
sqliteDB_ = std::make_unique<DatabaseCon>(
|
|
downloadDir_,
|
|
stateDBName,
|
|
DownloaderDBPragma,
|
|
ShardArchiveHandlerDBInit);
|
|
|
|
auto& session{sqliteDB_->getSession()};
|
|
|
|
soci::rowset<soci::row> rs =
|
|
(session.prepare << "SELECT * FROM State;");
|
|
|
|
for (auto it = rs.begin(); it != rs.end(); ++it)
|
|
{
|
|
parsedURL url;
|
|
|
|
if (!parseUrl(url, it->get<std::string>(1)))
|
|
{
|
|
JLOG(j_.error())
|
|
<< "Failed to parse url: " << it->get<std::string>(1);
|
|
|
|
continue;
|
|
}
|
|
|
|
add(it->get<int>(0), std::move(url), lock);
|
|
}
|
|
|
|
// Failed to load anything
|
|
// from the state database.
|
|
if (archives_.empty())
|
|
{
|
|
release();
|
|
return false;
|
|
}
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "exception: " << e.what()
|
|
<< " in function: " << __func__;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ShardArchiveHandler::onStop()
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_);
|
|
|
|
if (downloader_)
|
|
{
|
|
downloader_->onStop();
|
|
downloader_.reset();
|
|
}
|
|
|
|
timer_.cancel();
|
|
}
|
|
|
|
jobCounter_.join(
|
|
"ShardArchiveHandler", std::chrono::milliseconds(2000), j_);
|
|
|
|
timerCounter_.join(
|
|
"ShardArchiveHandler", std::chrono::milliseconds(2000), j_);
|
|
|
|
stopped();
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::add(
|
|
std::uint32_t shardIndex,
|
|
std::pair<parsedURL, std::string>&& url)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_);
|
|
|
|
if (!add(shardIndex, std::forward<parsedURL>(url.first), lock))
|
|
return false;
|
|
|
|
auto& session{sqliteDB_->getSession()};
|
|
|
|
session << "INSERT INTO State VALUES (:index, :url);",
|
|
soci::use(shardIndex), soci::use(url.second);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::add(
|
|
std::uint32_t shardIndex,
|
|
parsedURL&& url,
|
|
std::lock_guard<std::mutex> const&)
|
|
{
|
|
if (process_)
|
|
{
|
|
JLOG(j_.error()) << "Download and import already in progress";
|
|
return false;
|
|
}
|
|
|
|
auto const it{archives_.find(shardIndex)};
|
|
if (it != archives_.end())
|
|
return url == it->second;
|
|
|
|
if (!app_.getShardStore()->prepareShard(shardIndex))
|
|
return false;
|
|
|
|
archives_.emplace(shardIndex, std::move(url));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::start()
|
|
{
|
|
std::lock_guard lock(m_);
|
|
if (!app_.getShardStore())
|
|
{
|
|
JLOG(j_.error()) << "No shard store available";
|
|
return false;
|
|
}
|
|
if (process_)
|
|
{
|
|
JLOG(j_.warn()) << "Archives already being processed";
|
|
return false;
|
|
}
|
|
if (archives_.empty())
|
|
{
|
|
JLOG(j_.warn()) << "No archives to process";
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Create temp root download directory
|
|
create_directories(downloadDir_);
|
|
|
|
if (!downloader_)
|
|
{
|
|
// will throw if can't initialize ssl context
|
|
downloader_ = std::make_unique<DatabaseDownloader>(
|
|
app_.getIOService(), j_, app_.config());
|
|
}
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "exception: " << e.what();
|
|
return false;
|
|
}
|
|
|
|
process_ = true;
|
|
return next(lock);
|
|
}
|
|
|
|
void
|
|
ShardArchiveHandler::release()
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_);
|
|
doRelease(lock);
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::next(std::lock_guard<std::mutex> const& l)
|
|
{
|
|
if (archives_.empty())
|
|
{
|
|
doRelease(l);
|
|
return false;
|
|
}
|
|
|
|
if (isStopping())
|
|
return false;
|
|
|
|
auto const shardIndex{archives_.begin()->first};
|
|
|
|
// We use the sequence of the last validated ledger
|
|
// to determine whether or not we have stored a ledger
|
|
// that comes after the last ledger in this shard. A
|
|
// later ledger must be present in order to reliably
|
|
// retrieve the hash of the shard's last ledger.
|
|
boost::optional<uint256> expectedHash;
|
|
bool shouldHaveHash = false;
|
|
if (auto const seq = app_.getShardStore()->lastLedgerSeq(shardIndex);
|
|
(shouldHaveHash = app_.getLedgerMaster().getValidLedgerIndex() > seq))
|
|
{
|
|
expectedHash = app_.getLedgerMaster().walkHashBySeq(
|
|
seq, InboundLedger::Reason::GENERIC);
|
|
}
|
|
|
|
if (!expectedHash)
|
|
{
|
|
auto wrapper =
|
|
timerCounter_.wrap([this](boost::system::error_code const& ec) {
|
|
if (ec != boost::asio::error::operation_aborted)
|
|
{
|
|
std::lock_guard lock(m_);
|
|
this->next(lock);
|
|
}
|
|
});
|
|
|
|
if (!wrapper)
|
|
return onClosureFailed(
|
|
"failed to wrap closure for last ledger confirmation timer", l);
|
|
|
|
if (!verificationScheduler_.retry(app_, shouldHaveHash, *wrapper))
|
|
{
|
|
JLOG(j_.error()) << "failed to find last ledger hash for shard "
|
|
<< shardIndex << ", maximum attempts reached";
|
|
|
|
return removeAndProceed(l);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Create a temp archive directory at the root
|
|
auto const dstDir{downloadDir_ / std::to_string(shardIndex)};
|
|
try
|
|
{
|
|
create_directory(dstDir);
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "exception: " << e.what();
|
|
return removeAndProceed(l);
|
|
}
|
|
|
|
// Download the archive. Process in another thread
|
|
// to prevent holding up the lock if the downloader
|
|
// sleeps.
|
|
auto const& url{archives_.begin()->second};
|
|
auto wrapper = jobCounter_.wrap([this, url, dstDir](Job&) {
|
|
if (!downloader_->download(
|
|
url.domain,
|
|
std::to_string(url.port.get_value_or(443)),
|
|
url.path,
|
|
11,
|
|
dstDir / "archive.tar.lz4",
|
|
[this](path dstPath) { complete(dstPath); }))
|
|
{
|
|
std::lock_guard<std::mutex> l(m_);
|
|
removeAndProceed(l);
|
|
}
|
|
});
|
|
|
|
if (!wrapper)
|
|
return onClosureFailed(
|
|
"failed to wrap closure for starting download", l);
|
|
|
|
app_.getJobQueue().addJob(jtCLIENT, "ShardArchiveHandler", *wrapper);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ShardArchiveHandler::complete(path dstPath)
|
|
{
|
|
if (isStopping())
|
|
return;
|
|
|
|
{
|
|
std::lock_guard lock(m_);
|
|
try
|
|
{
|
|
if (!is_regular_file(dstPath))
|
|
{
|
|
auto ar{archives_.begin()};
|
|
JLOG(j_.error())
|
|
<< "Downloading shard id " << ar->first << " from URL "
|
|
<< ar->second.domain << ar->second.path;
|
|
removeAndProceed(lock);
|
|
return;
|
|
}
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "exception: " << e.what();
|
|
removeAndProceed(lock);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Make lambdas mutable captured vars can be moved from
|
|
auto wrapper =
|
|
jobCounter_.wrap([=, dstPath = std::move(dstPath)](Job&) mutable {
|
|
if (isStopping())
|
|
return;
|
|
|
|
// If not synced then defer and retry
|
|
auto const mode{app_.getOPs().getOperatingMode()};
|
|
if (mode != OperatingMode::FULL)
|
|
{
|
|
std::lock_guard lock(m_);
|
|
timer_.expires_from_now(static_cast<std::chrono::seconds>(
|
|
(static_cast<std::size_t>(OperatingMode::FULL) -
|
|
static_cast<std::size_t>(mode)) *
|
|
10));
|
|
|
|
auto wrapper = timerCounter_.wrap(
|
|
[=, dstPath = std::move(dstPath)](
|
|
boost::system::error_code const& ec) mutable {
|
|
if (ec != boost::asio::error::operation_aborted)
|
|
complete(std::move(dstPath));
|
|
});
|
|
|
|
if (!wrapper)
|
|
onClosureFailed(
|
|
"failed to wrap closure for operating mode timer",
|
|
lock);
|
|
else
|
|
timer_.async_wait(*wrapper);
|
|
}
|
|
else
|
|
{
|
|
process(dstPath);
|
|
std::lock_guard lock(m_);
|
|
removeAndProceed(lock);
|
|
}
|
|
});
|
|
|
|
if (!wrapper)
|
|
{
|
|
if (isStopping())
|
|
return;
|
|
|
|
JLOG(j_.error()) << "failed to wrap closure for process()";
|
|
|
|
std::lock_guard lock(m_);
|
|
removeAndProceed(lock);
|
|
}
|
|
|
|
// Process in another thread to not hold up the IO service
|
|
app_.getJobQueue().addJob(jtCLIENT, "ShardArchiveHandler", *wrapper);
|
|
}
|
|
|
|
void
|
|
ShardArchiveHandler::process(path const& dstPath)
|
|
{
|
|
std::uint32_t shardIndex;
|
|
{
|
|
std::lock_guard lock(m_);
|
|
shardIndex = archives_.begin()->first;
|
|
}
|
|
|
|
auto const shardDir{dstPath.parent_path() / std::to_string(shardIndex)};
|
|
try
|
|
{
|
|
// Extract the downloaded archive
|
|
extractTarLz4(dstPath, dstPath.parent_path());
|
|
|
|
// The extracted root directory name must match the shard index
|
|
if (!is_directory(shardDir))
|
|
{
|
|
JLOG(j_.error()) << "Shard " << shardIndex
|
|
<< " mismatches archive shard directory";
|
|
return;
|
|
}
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "exception: " << e.what();
|
|
return;
|
|
}
|
|
|
|
// Import the shard into the shard store
|
|
if (!app_.getShardStore()->importShard(shardIndex, shardDir))
|
|
{
|
|
JLOG(j_.error()) << "Importing shard " << shardIndex;
|
|
return;
|
|
}
|
|
|
|
JLOG(j_.debug()) << "Shard " << shardIndex << " downloaded and imported";
|
|
}
|
|
|
|
void
|
|
ShardArchiveHandler::remove(std::lock_guard<std::mutex> const&)
|
|
{
|
|
verificationScheduler_.reset();
|
|
|
|
auto const shardIndex{archives_.begin()->first};
|
|
app_.getShardStore()->removePreShard(shardIndex);
|
|
archives_.erase(shardIndex);
|
|
|
|
auto& session{sqliteDB_->getSession()};
|
|
|
|
session << "DELETE FROM State WHERE ShardIndex = :index;",
|
|
soci::use(shardIndex);
|
|
|
|
auto const dstDir{downloadDir_ / std::to_string(shardIndex)};
|
|
try
|
|
{
|
|
remove_all(dstDir);
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "exception: " << e.what();
|
|
}
|
|
}
|
|
|
|
void
|
|
ShardArchiveHandler::doRelease(std::lock_guard<std::mutex> const&)
|
|
{
|
|
timer_.cancel();
|
|
for (auto const& ar : archives_)
|
|
app_.getShardStore()->removePreShard(ar.first);
|
|
archives_.clear();
|
|
|
|
{
|
|
auto& session{sqliteDB_->getSession()};
|
|
|
|
session << "DROP TABLE State;";
|
|
}
|
|
|
|
sqliteDB_.reset();
|
|
|
|
// Remove temp root download directory
|
|
try
|
|
{
|
|
remove_all(downloadDir_);
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "exception: " << e.what()
|
|
<< " in function: " << __func__;
|
|
}
|
|
|
|
downloader_.reset();
|
|
process_ = false;
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::onClosureFailed(
|
|
std::string const& errorMsg,
|
|
std::lock_guard<std::mutex> const& lock)
|
|
{
|
|
if (isStopping())
|
|
return false;
|
|
|
|
JLOG(j_.error()) << errorMsg;
|
|
|
|
return removeAndProceed(lock);
|
|
}
|
|
|
|
bool
|
|
ShardArchiveHandler::removeAndProceed(std::lock_guard<std::mutex> const& lock)
|
|
{
|
|
remove(lock);
|
|
return next(lock);
|
|
}
|
|
|
|
RecoveryHandler::RecoveryHandler(Application& app, Stoppable& parent)
|
|
: ShardArchiveHandler(app, parent)
|
|
{
|
|
}
|
|
|
|
} // namespace RPC
|
|
} // namespace ripple
|