1#include <xrpld/app/misc/ValidatorSite.h>
3#include <xrpld/app/main/Application.h>
4#include <xrpld/app/misc/ValidatorList.h>
5#include <xrpld/app/misc/detail/Work.h>
6#include <xrpld/app/misc/detail/WorkFile.h>
7#include <xrpld/app/misc/detail/WorkPlain.h>
8#include <xrpld/app/misc/detail/WorkSSL.h>
10#include <xrpl/basics/Log.h>
11#include <xrpl/basics/StringUtilities.h>
12#include <xrpl/basics/chrono.h>
13#include <xrpl/beast/utility/Journal.h>
14#include <xrpl/beast/utility/instrumentation.h>
15#include <xrpl/json/json_reader.h>
16#include <xrpl/json/json_value.h>
17#include <xrpl/protocol/digest.h>
18#include <xrpl/protocol/jss.h>
20#include <boost/asio/error.hpp>
21#include <boost/beast/http/field.hpp>
22#include <boost/beast/http/impl/serializer.hpp>
23#include <boost/beast/http/status.hpp>
24#include <boost/system/detail/error_code.hpp>
25#include <boost/system/detail/generic_category.hpp>
26#include <boost/system/system_error.hpp>
55 if (
pUrl.scheme ==
"file")
57 if (!pUrl.domain.empty())
58 throw std::runtime_error(
"file URI cannot contain a hostname");
62 if (pUrl.path[0] ==
'/')
63 pUrl.path = pUrl.path.substr(1);
66 if (pUrl.path.empty())
67 throw std::runtime_error(
"file URI must contain a path");
69 else if (
pUrl.scheme ==
"http")
71 if (pUrl.domain.empty())
72 throw std::runtime_error(
"http URI must contain a hostname");
77 else if (
pUrl.scheme ==
"https")
79 if (pUrl.domain.empty())
80 throw std::runtime_error(
"https URI must contain a hostname");
87 throw std::runtime_error(
"Unsupported scheme: '" + pUrl.scheme +
"'");
105 ,
j_{j ? *j :
app_.getJournal(
"ValidatorSite")}
117 if (
timer_.expiry() > ClockType::time_point{})
134 auto const sites =
app_.getValidators().loadLists();
135 return sites.empty() ||
load(sites, lockSites);
141 JLOG(
j_.debug()) <<
"Loading configured validator list sites";
145 return load(siteURIs, lock);
154 if (siteURIs.
empty())
159 for (
auto const& uri : siteURIs)
167 JLOG(
j_.error()) <<
"Invalid validator site uri: " << uri <<
": " << e.
what();
172 JLOG(
j_.debug()) <<
"Loaded " << siteURIs.
size() <<
" sites";
182 if (
timer_.expiry() == ClockType::time_point{})
201 if (
auto sp =
work_.lock())
211 catch (boost::system::system_error
const&)
231 timer_.expires_at(next->nextRefresh);
234 [
this, idx](boost::system::error_code
const& ec) { this->
onTimer(idx, ec); });
247 auto timeoutCancel = [
this]() {
255 catch (boost::system::system_error
const&)
260 [
this, siteIdx, timeoutCancel](
266 auto onFetchFile = [
this, siteIdx, timeoutCancel](
272 JLOG(
j_.debug()) <<
"Starting request for " <<
resource->uri;
274 if (
resource->pUrl.scheme ==
"https")
285 sites_[siteIdx].lastRequestEndpoint,
286 sites_[siteIdx].lastRequestSuccessful,
289 else if (
resource->pUrl.scheme ==
"http")
297 sites_[siteIdx].lastRequestEndpoint,
298 sites_[siteIdx].lastRequestSuccessful,
303 BOOST_ASSERT(
resource->pUrl.scheme ==
"file");
308 sites_[siteIdx].lastRequestSuccessful =
false;
315 timer_.async_wait([
this, siteIdx](boost::system::error_code
const& ec) {
334 auto const& site =
sites_[siteIdx];
335 if (site.activeResource)
337 JLOG(
j_.warn()) <<
"Request for " << site.activeResource->uri <<
" took too long";
341 JLOG(
j_.error()) <<
"Request took too long, but a response has "
342 "already been processed";
347 if (
auto sp =
work_.lock())
358 if (ec != boost::asio::error::operation_aborted)
367 sites_[siteIdx].redirCount = 0;
373 JLOG(
j_.error()) <<
"Exception in " << __func__ <<
": " << ex.
what();
375 boost::system::error_code{-1, boost::system::generic_category()},
391 if (!r.
parse(res, body))
393 JLOG(
j_.warn()) <<
"Unable to parse JSON response from "
394 <<
sites_[siteIdx].activeResource->uri;
400 auto const [
valid, version, blobs] = [&body]() {
404 body[jss::version].
isInt();
410 version = body[jss::version].
asUInt();
419 JLOG(
j_.warn()) <<
"Missing fields in JSON response from "
420 <<
sites_[siteIdx].activeResource->uri;
424 auto const manifest = body[jss::manifest].
asString();
426 version == body[jss::version].asUInt(),
427 "xrpl::ValidatorSite::parseJsonResponse : version match");
428 auto const& uri =
sites_[siteIdx].activeResource->uri;
429 auto const hash =
sha512Half(manifest, blobs, version);
430 auto const applyResult =
app_.getValidators().applyListsAndBroadcast(
437 app_.getHashRouter(),
440 sites_[siteIdx].lastRefreshStatus.emplace(
443 .disposition = applyResult.bestDisposition(),
446 for (
auto const& [disp, count] : applyResult.dispositions)
451 JLOG(
j_.debug()) <<
"Applied " << count <<
" new validator list(s) from " << uri;
454 JLOG(
j_.debug()) <<
"Applied " << count <<
" expired validator list(s) from "
458 JLOG(
j_.debug()) <<
"Ignored " << count
459 <<
" validator list(s) with current sequence from " << uri;
462 JLOG(
j_.debug()) <<
"Processed " << count <<
" future validator list(s) from "
466 JLOG(
j_.debug()) <<
"Ignored " << count
467 <<
" validator list(s) with future known sequence from " << uri;
470 JLOG(
j_.warn()) <<
"Ignored " << count <<
"stale validator list(s) from " << uri;
473 JLOG(
j_.warn()) <<
"Ignored " << count <<
" untrusted validator list(s) from "
477 JLOG(
j_.warn()) <<
"Ignored " << count <<
" invalid validator list(s) from " << uri;
480 JLOG(
j_.warn()) <<
"Ignored " << count
481 <<
" unsupported version validator list(s) from " << uri;
488 if (body.
isMember(jss::refresh_interval) && body[jss::refresh_interval].
isNumeric())
490 using namespace std::chrono_literals;
495 sites_[siteIdx].refreshInterval = refresh;
506 using namespace boost::beast::http;
508 if (!res.contains(field::location) || res[field::location].empty())
510 JLOG(
j_.warn()) <<
"Request for validator list at " <<
sites_[siteIdx].activeResource->uri
511 <<
" returned a redirect with no Location.";
517 JLOG(
j_.warn()) <<
"Exceeded max redirects for validator list at "
518 <<
sites_[siteIdx].loadedResource->uri;
522 JLOG(
j_.debug()) <<
"Got redirect for validator list from "
523 <<
sites_[siteIdx].activeResource->uri <<
" to new location "
524 << res[field::location];
529 ++
sites_[siteIdx].redirCount;
530 if (newLocation->pUrl.scheme !=
"http" && newLocation->pUrl.scheme !=
"https")
535 JLOG(
j_.error()) <<
"Invalid redirect location: " << res[field::location];
543 boost::system::error_code
const& ec,
551 sites_[siteIdx].lastRequestEndpoint = endpoint;
552 JLOG(
j_.debug()) <<
"Got completion for " <<
sites_[siteIdx].activeResource->uri <<
" "
554 auto onError = [&](
std::string const& errMsg,
bool retry) {
555 sites_[siteIdx].lastRefreshStatus.emplace(
569 JLOG(
j_.warn()) <<
"Problem retrieving from " <<
sites_[siteIdx].activeResource->uri
570 <<
" " << endpoint <<
" " << ec.value() <<
":" << ec.message();
571 onError(
"fetch error",
true);
577 using namespace boost::beast::http;
578 switch (res.result())
581 sites_[siteIdx].lastRequestSuccessful =
true;
584 case status::moved_permanently:
585 case status::permanent_redirect:
587 case status::temporary_redirect: {
591 "xrpl::ValidatorSite::onSiteFetch : non-null "
594 if (res.result() == status::moved_permanently ||
595 res.result() == status::permanent_redirect)
597 sites_[siteIdx].startingResource = newLocation;
604 JLOG(
j_.warn()) <<
"Request for validator list at "
605 <<
sites_[siteIdx].activeResource->uri <<
" " << endpoint
606 <<
" returned bad status: " << res.result_int();
607 onError(
"bad result code",
true);
613 JLOG(
j_.error()) <<
"Exception in " << __func__ <<
": " << ex.
what();
614 onError(ex.
what(),
false);
617 sites_[siteIdx].activeResource.reset();
629 boost::system::error_code
const& ec,
639 JLOG(
j_.warn()) <<
"Problem retrieving from " <<
sites_[siteIdx].activeResource->uri
640 <<
" " << ec.value() <<
": " << ec.message();
644 sites_[siteIdx].lastRequestSuccessful =
true;
650 JLOG(
j_.error()) <<
"Exception in " << __func__ <<
": " << ex.
what();
651 sites_[siteIdx].lastRefreshStatus.emplace(
655 .message = ex.
what()});
657 sites_[siteIdx].activeResource.reset();
681 uri << site.loadedResource->uri;
682 if (site.loadedResource != site.startingResource)
683 uri <<
" (redirects to " << site.startingResource->uri +
")";
684 v[jss::uri] = uri.
str();
685 v[jss::next_refresh_time] =
to_string(site.nextRefresh);
686 if (site.lastRefreshStatus)
688 v[jss::last_refresh_time] =
to_string(site.lastRefreshStatus->refreshed);
689 v[jss::last_refresh_status] =
to_string(site.lastRefreshStatus->disposition);
690 if (!site.lastRefreshStatus->message.empty())
691 v[jss::last_refresh_message] = site.lastRefreshStatus->message;
693 v[jss::refresh_interval_min] =
static_cast<Int
>(site.refreshInterval.count());
Unserialize a JSON document into a Value.
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Value & append(Value const &value)
Append value to array at the end.
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
static std::vector< ValidatorBlobInfo > parseBlobs(std::uint32_t version, json::Value const &body)
Pull the blob/signature/manifest information out of the appropriate Json body fields depending on the...
bool missingSite(std::scoped_lock< std::mutex > const &)
If no sites are provided, or a site fails to load, get a list of local cache files from the Validator...
boost::asio::basic_waitable_timer< ClockType > timer_
void onRequestTimeout(std::size_t siteIdx, ErrorCode const &ec)
request took too long
void onTimer(std::size_t siteIdx, ErrorCode const &ec)
Fetch site whose time has come.
json::Value getJson() const
Return JSON representation of configured validator sites.
bool load(std::vector< std::string > const &siteURIs)
Load configured site URIs.
void join()
Wait for current fetches from sites to complete.
std::shared_ptr< Site::Resource > processRedirect(detail::ResponseType const &res, std::size_t siteIdx, std::scoped_lock< std::mutex > const &)
Interpret a redirect response.
void onSiteFetch(boost::system::error_code const &ec, EndpointType const &endpoint, detail::ResponseType const &res, std::size_t siteIdx)
Store latest list fetched from site.
std::atomic< bool > pending_
std::atomic< bool > fetching_
void setTimer(std::scoped_lock< std::mutex > const &, std::scoped_lock< std::mutex > const &)
Queue next site to be fetched lock over site_mutex_ and state_mutex_ required.
void start()
Start fetching lists from sites.
std::chrono::seconds const requestTimeout_
void makeRequest(std::shared_ptr< Site::Resource > resource, std::size_t siteIdx, std::scoped_lock< std::mutex > const &)
Initiate request to given resource.
boost::asio::ip::tcp::endpoint EndpointType
std::condition_variable cv_
boost::system::error_code ErrorCode
std::atomic< bool > stopping_
void parseJsonResponse(std::string const &res, std::size_t siteIdx, std::scoped_lock< std::mutex > const &)
Parse json response from validator list site.
ValidatorSite(Application &app, std::optional< beast::Journal > j=std::nullopt, std::chrono::seconds timeout=std::chrono::seconds{20})
std::weak_ptr< detail::Work > work_
void onTextFetch(boost::system::error_code const &ec, std::string const &res, std::size_t siteIdx)
Store latest list fetched from anywhere.
std::chrono::system_clock ClockType
void stop()
Stop fetching lists from sites.
std::vector< Site > sites_
@ Array
array value (ordered list)
@ Object
object value (collection of name/value pairs).
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
boost::beast::http::response< boost::beast::http::string_body > ResponseType
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
constexpr auto kErrorRetryInterval
@ UnsupportedVersion
List version is not supported.
@ Expired
List is expired, but has the largest non-pending sequence seen so far.
@ SameSequence
Same sequence as current list.
@ KnownSequence
Future sequence already seen.
@ Pending
List will be valid in the future.
@ Invalid
Invalid format or signature.
@ Untrusted
List signed by untrusted publisher key.
@ Stale
Trusted publisher key, but seq is too old.
std::string to_string(BaseUInt< Bits, Tag > const &a)
unsigned constexpr short kMaxRedirects
constexpr auto kDefaultRefreshInterval
bool parseUrl(ParsedUrl &pUrl, std::string const &strUrl)
Sha512HalfHasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Resource(std::string uri)
std::shared_ptr< Resource > startingResource
the resource to request at <timer> intervals.
std::chrono::minutes refreshInterval
ClockType::time_point nextRefresh
std::shared_ptr< Resource > loadedResource
the original uri as loaded from config