20#include <xrpld/app/misc/ValidatorList.h>
21#include <xrpld/app/misc/ValidatorSite.h>
22#include <xrpld/app/misc/detail/WorkFile.h>
23#include <xrpld/app/misc/detail/WorkPlain.h>
24#include <xrpld/app/misc/detail/WorkSSL.h>
25#include <xrpl/json/json_reader.h>
26#include <xrpl/protocol/digest.h>
27#include <xrpl/protocol/jss.h>
92 ,
j_{j ? *j :
app_.logs().journal(
"ValidatorSite")}
104 if (
timer_.expires_at() > clock_type::time_point{})
122 return sites.
empty() ||
load(sites, lock_sites);
128 JLOG(
j_.
debug()) <<
"Loading configured validator list sites";
132 return load(siteURIs, lock);
141 if (siteURIs.
empty())
146 for (
auto const& uri : siteURIs)
155 <<
"Invalid validator site uri: " << uri <<
": " << e.
what();
160 JLOG(
j_.
debug()) <<
"Loaded " << siteURIs.
size() <<
" sites";
170 if (
timer_.expires_at() == clock_type::time_point{})
189 if (
auto sp =
work_.lock())
199 catch (boost::system::system_error
const&)
214 return a.nextRefresh < b.nextRefresh;
221 timer_.expires_at(next->nextRefresh);
223 timer_.async_wait([
this, idx](boost::system::error_code
const& ec) {
236 sites_[siteIdx].activeResource = resource;
238 auto timeoutCancel = [
this]() {
246 catch (boost::system::system_error
const&)
250 auto onFetch = [
this, siteIdx, timeoutCancel](
255 onSiteFetch(err, endpoint, std::move(resp), siteIdx);
258 auto onFetchFile = [
this, siteIdx, timeoutCancel](
264 JLOG(
j_.
debug()) <<
"Starting request for " << resource->uri;
266 if (resource->pUrl.scheme ==
"https")
269 sp = std::make_shared<detail::WorkSSL>(
270 resource->pUrl.domain,
276 sites_[siteIdx].lastRequestEndpoint,
277 sites_[siteIdx].lastRequestSuccessful,
280 else if (resource->pUrl.scheme ==
"http")
282 sp = std::make_shared<detail::WorkPlain>(
283 resource->pUrl.domain,
287 sites_[siteIdx].lastRequestEndpoint,
288 sites_[siteIdx].lastRequestSuccessful,
293 BOOST_ASSERT(resource->pUrl.scheme ==
"file");
294 sp = std::make_shared<detail::WorkFile>(
298 sites_[siteIdx].lastRequestSuccessful =
false;
305 timer_.async_wait([
this, siteIdx](boost::system::error_code
const& ec) {
324 auto const& site =
sites_[siteIdx];
325 if (site.activeResource)
326 JLOG(
j_.
warn()) <<
"Request for " << site.activeResource->uri
329 JLOG(
j_.
error()) <<
"Request took too long, but a response has "
330 "already been processed";
334 if (
auto sp =
work_.lock())
345 if (ec != boost::asio::error::operation_aborted)
353 sites_[siteIdx].nextRefresh =
355 sites_[siteIdx].redirCount = 0;
361 JLOG(
j_.
error()) <<
"Exception in " << __func__ <<
": " << ex.
what();
363 boost::system::error_code{-1, boost::system::generic_category()},
381 JLOG(
j_.
warn()) <<
"Unable to parse JSON response from "
382 <<
sites_[siteIdx].activeResource->uri;
388 auto const [
valid, version, blobs] = [&body]() {
392 body[jss::version].
isInt();
398 version = body[jss::version].
asUInt();
407 JLOG(
j_.
warn()) <<
"Missing fields in JSON response from "
408 <<
sites_[siteIdx].activeResource->uri;
414 version == body[jss::version].asUInt(),
415 "ripple::ValidatorSite::parseJsonResponse : version match");
416 auto const& uri =
sites_[siteIdx].activeResource->uri;
428 sites_[siteIdx].lastRefreshStatus.emplace(
431 for (
auto const& [disp, count] : applyResult.dispositions)
436 JLOG(
j_.
debug()) <<
"Applied " << count
437 <<
" new validator list(s) from " << uri;
440 JLOG(
j_.
debug()) <<
"Applied " << count
441 <<
" expired validator list(s) from " << uri;
445 <<
"Ignored " << count
446 <<
" validator list(s) with current sequence from " << uri;
449 JLOG(
j_.
debug()) <<
"Processed " << count
450 <<
" future validator list(s) from " << uri;
454 <<
"Ignored " << count
455 <<
" validator list(s) with future known sequence from "
459 JLOG(
j_.
warn()) <<
"Ignored " << count
460 <<
"stale validator list(s) from " << uri;
463 JLOG(
j_.
warn()) <<
"Ignored " << count
464 <<
" untrusted validator list(s) from " << uri;
467 JLOG(
j_.
warn()) <<
"Ignored " << count
468 <<
" invalid validator list(s) from " << uri;
472 <<
"Ignored " << count
473 <<
" unsupported version validator list(s) from " << uri;
480 if (body.
isMember(jss::refresh_interval) &&
483 using namespace std::chrono_literals;
488 sites_[siteIdx].refreshInterval = refresh;
489 sites_[siteIdx].nextRefresh =
500 using namespace boost::beast::http;
502 if (res.find(field::location) == res.end() || res[field::location].empty())
504 JLOG(
j_.
warn()) <<
"Request for validator list at "
505 <<
sites_[siteIdx].activeResource->uri
506 <<
" returned a redirect with no Location.";
512 JLOG(
j_.
warn()) <<
"Exceeded max redirects for validator list at "
513 <<
sites_[siteIdx].loadedResource->uri;
517 JLOG(
j_.
debug()) <<
"Got redirect for validator list from "
518 <<
sites_[siteIdx].activeResource->uri
519 <<
" to new location " << res[field::location];
524 std::make_shared<Site::Resource>(
std::string(res[field::location]));
525 ++
sites_[siteIdx].redirCount;
526 if (newLocation->pUrl.scheme !=
"http" &&
527 newLocation->pUrl.scheme !=
"https")
529 "invalid scheme in redirect " + newLocation->pUrl.scheme);
533 JLOG(
j_.
error()) <<
"Invalid redirect location: "
534 << res[field::location];
542 boost::system::error_code
const& ec,
550 sites_[siteIdx].lastRequestEndpoint = endpoint;
551 JLOG(
j_.
debug()) <<
"Got completion for "
552 <<
sites_[siteIdx].activeResource->uri <<
" "
554 auto onError = [&](
std::string const& errMsg,
bool retry) {
558 sites_[siteIdx].nextRefresh =
568 <<
"Problem retrieving from "
569 <<
sites_[siteIdx].activeResource->uri <<
" " << endpoint <<
" "
570 << 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: {
592 "ripple::ValidatorSite::onSiteFetch : non-null "
595 if (res.result() == status::moved_permanently ||
596 res.result() == status::permanent_redirect)
598 sites_[siteIdx].startingResource = newLocation;
606 <<
"Request for validator list at "
607 <<
sites_[siteIdx].activeResource->uri <<
" "
609 <<
" returned bad status: " << res.result_int();
610 onError(
"bad result code",
true);
617 <<
"Exception in " << __func__ <<
": " << ex.
what();
618 onError(ex.
what(),
false);
621 sites_[siteIdx].activeResource.reset();
633 boost::system::error_code
const& ec,
643 JLOG(
j_.
warn()) <<
"Problem retrieving from "
644 <<
sites_[siteIdx].activeResource->uri <<
" "
645 << ec.value() <<
": " << ec.message();
649 sites_[siteIdx].lastRequestSuccessful =
true;
656 <<
"Exception in " << __func__ <<
": " << ex.
what();
660 sites_[siteIdx].activeResource.reset();
684 uri << site.loadedResource->uri;
685 if (site.loadedResource != site.startingResource)
686 uri <<
" (redirects to " << site.startingResource->uri +
")";
687 v[jss::uri] = uri.
str();
688 v[jss::next_refresh_time] =
to_string(site.nextRefresh);
689 if (site.lastRefreshStatus)
691 v[jss::last_refresh_time] =
692 to_string(site.lastRefreshStatus->refreshed);
693 v[jss::last_refresh_status] =
694 to_string(site.lastRefreshStatus->disposition);
695 if (!site.lastRefreshStatus->message.empty())
696 v[jss::last_refresh_message] =
697 site.lastRefreshStatus->message;
699 v[jss::refresh_interval_min] =
700 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(const Value &value)
Append value to array at the end.
std::string asString() const
Returns the unquoted string value.
bool isMember(const char *key) const
Return true if the object has a member named key.
virtual Config & config()=0
virtual Overlay & overlay()=0
virtual NetworkOPs & getOPs()=0
virtual ValidatorList & validators()=0
virtual boost::asio::io_service & getIOService()=0
virtual HashRouter & getHashRouter()=0
std::vector< std::string > loadLists()
PublisherListStats applyListsAndBroadcast(std::string const &manifest, std::uint32_t version, std::vector< ValidatorBlobInfo > const &blobs, std::string siteUri, uint256 const &hash, Overlay &overlay, HashRouter &hashRouter, NetworkOPs &networkOPs)
Apply multiple published lists of public keys, then broadcast it to all peers that have not seen it o...
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...
void start()
Start fetching lists from sites.
std::condition_variable cv_
std::vector< Site > sites_
boost::asio::ip::tcp::endpoint endpoint_type
void stop()
Stop fetching lists from sites.
Json::Value getJson() const
Return JSON representation of configured validator sites.
bool load(std::vector< std::string > const &siteURIs)
Load configured site URIs.
void onTextFetch(boost::system::error_code const &ec, std::string const &res, std::size_t siteIdx)
Store latest list fetched from anywhere.
std::weak_ptr< detail::Work > work_
void setTimer(std::lock_guard< std::mutex > const &, std::lock_guard< std::mutex > const &)
Queue next site to be fetched lock over site_mutex_ and state_mutex_ required.
ValidatorSite(Application &app, std::optional< beast::Journal > j=std::nullopt, std::chrono::seconds timeout=std::chrono::seconds{20})
std::atomic< bool > stopping_
void join()
Wait for current fetches from sites to complete.
bool missingSite(std::lock_guard< std::mutex > const &)
If no sites are provided, or a site fails to load, get a list of local cache files from the Validator...
std::shared_ptr< Site::Resource > processRedirect(detail::response_type &res, std::size_t siteIdx, std::lock_guard< std::mutex > const &)
Interpret a redirect response.
void parseJsonResponse(std::string const &res, std::size_t siteIdx, std::lock_guard< std::mutex > const &)
Parse json response from validator list site.
void makeRequest(std::shared_ptr< Site::Resource > resource, std::size_t siteIdx, std::lock_guard< std::mutex > const &)
Initiate request to given resource.
void onRequestTimeout(std::size_t siteIdx, error_code const &ec)
request took too long
std::atomic< bool > pending_
boost::system::error_code error_code
boost::asio::basic_waitable_timer< clock_type > timer_
void onTimer(std::size_t siteIdx, error_code const &ec)
Fetch site whose time has come.
void onSiteFetch(boost::system::error_code const &ec, endpoint_type const &endpoint, detail::response_type &&res, std::size_t siteIdx)
Store latest list fetched from site.
const std::chrono::seconds requestTimeout_
std::atomic< bool > fetching_
@ arrayValue
array value (ordered list)
@ objectValue
object value (collection of name/value pairs).
TER valid(PreclaimContext const &ctx, AccountID const &src)
boost::beast::http::response< boost::beast::http::string_body > response_type
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
@ unsupported_version
List version is not supported.
@ stale
Trusted publisher key, but seq is too old.
@ untrusted
List signed by untrusted publisher key.
@ same_sequence
Same sequence as current list.
@ pending
List will be valid in the future.
@ known_sequence
Future sequence already seen.
@ expired
List is expired, but has the largest non-pending sequence seen so far.
@ invalid
Invalid format or signature.
bool parseUrl(parsedURL &pUrl, std::string const &strUrl)
std::string to_string(base_uint< Bits, Tag > const &a)
unsigned short constexpr max_redirects
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
auto constexpr error_retry_interval
auto constexpr default_refresh_interval
Resource(std::string uri_)
std::shared_ptr< Resource > loadedResource
the original uri as loaded from config
std::shared_ptr< Resource > startingResource
the resource to request at <timer> intervals.
bool lastRequestSuccessful
endpoint_type lastRequestEndpoint
std::chrono::minutes refreshInterval
unsigned short redirCount
clock_type::time_point nextRefresh
std::optional< std::uint16_t > port