20 #include <ripple/app/misc/ValidatorList.h>
21 #include <ripple/app/misc/ValidatorSite.h>
22 #include <ripple/app/misc/detail/WorkFile.h>
23 #include <ripple/app/misc/detail/WorkPlain.h>
24 #include <ripple/app/misc/detail/WorkSSL.h>
25 #include <ripple/basics/Slice.h>
26 #include <ripple/basics/base64.h>
27 #include <ripple/json/json_reader.h>
28 #include <ripple/protocol/digest.h>
29 #include <ripple/protocol/jss.h>
43 if (pUrl.scheme ==
"file")
45 if (!pUrl.domain.empty())
50 if (pUrl.path[0] ==
'/')
51 pUrl.path = pUrl.path.substr(1);
54 if (pUrl.path.empty())
57 else if (pUrl.scheme ==
"http")
59 if (pUrl.domain.empty())
65 else if (pUrl.scheme ==
"https")
67 if (pUrl.domain.empty())
79 , startingResource{loadedResource}
83 , lastRequestEndpoint{}
84 , lastRequestSuccessful{
false}
93 , j_{j ? *j : app_.logs().journal(
"ValidatorSite")}
94 , timer_{app_.getIOService()}
98 , requestTimeout_{timeout}
105 if (
timer_.expires_at() > clock_type::time_point{})
123 return sites.
empty() ||
load(sites, lock_sites);
129 JLOG(
j_.
debug()) <<
"Loading configured validator list sites";
133 return load(siteURIs, lock);
142 if (siteURIs.
empty())
147 for (
auto const& uri : siteURIs)
156 <<
"Invalid validator site uri: " << uri <<
": " << e.
what();
161 JLOG(
j_.
debug()) <<
"Loaded " << siteURIs.size() <<
" sites";
171 if (
timer_.expires_at() == clock_type::time_point{})
190 if (
auto sp =
work_.lock())
200 catch (boost::system::system_error
const&)
215 return a.nextRefresh < b.nextRefresh;
222 timer_.expires_at(next->nextRefresh);
224 timer_.async_wait([
this, idx](boost::system::error_code
const& ec) {
237 sites_[siteIdx].activeResource = resource;
239 auto timeoutCancel = [
this]() {
247 catch (boost::system::system_error
const&)
251 auto onFetch = [
this, siteIdx, timeoutCancel](
256 onSiteFetch(err, endpoint, std::move(resp), siteIdx);
259 auto onFetchFile = [
this, siteIdx, timeoutCancel](
265 JLOG(
j_.
debug()) <<
"Starting request for " << resource->
uri;
270 sp = std::make_shared<detail::WorkSSL>(
277 sites_[siteIdx].lastRequestEndpoint,
278 sites_[siteIdx].lastRequestSuccessful,
283 sp = std::make_shared<detail::WorkPlain>(
288 sites_[siteIdx].lastRequestEndpoint,
289 sites_[siteIdx].lastRequestSuccessful,
294 BOOST_ASSERT(resource->
pUrl.
scheme ==
"file");
295 sp = std::make_shared<detail::WorkFile>(
299 sites_[siteIdx].lastRequestSuccessful =
false;
306 timer_.async_wait([
this, siteIdx](boost::system::error_code
const& ec) {
325 auto const& site =
sites_[siteIdx];
326 if (site.activeResource)
327 JLOG(
j_.
warn()) <<
"Request for " << site.activeResource->uri
330 JLOG(
j_.
error()) <<
"Request took too long, but a response has "
331 "already been processed";
335 if (
auto sp =
work_.lock())
346 if (ec != boost::asio::error::operation_aborted)
354 sites_[siteIdx].nextRefresh =
356 sites_[siteIdx].redirCount = 0;
362 JLOG(
j_.
error()) <<
"Exception in " << __func__ <<
": " << ex.
what();
364 boost::system::error_code{-1, boost::system::generic_category()},
382 JLOG(
j_.
warn()) <<
"Unable to parse JSON response from "
383 <<
sites_[siteIdx].activeResource->uri;
389 auto const [valid, version, blobs] = [&body]() {
393 body[jss::version].
isInt();
399 version = body[jss::version].
asUInt();
401 valid = !blobs.
empty();
408 JLOG(
j_.
warn()) <<
"Missing fields in JSON response from "
409 <<
sites_[siteIdx].activeResource->uri;
414 assert(version == body[jss::version].asUInt());
415 auto const& uri =
sites_[siteIdx].activeResource->uri;
427 sites_[siteIdx].lastRefreshStatus.emplace(
430 for (
auto const& [disp, count] : applyResult.dispositions)
435 JLOG(
j_.
debug()) <<
"Applied " << count
436 <<
" new validator list(s) from " << uri;
439 JLOG(
j_.
debug()) <<
"Applied " << count
440 <<
" expired validator list(s) from " << uri;
444 <<
"Ignored " << count
445 <<
" validator list(s) with current sequence from " << uri;
448 JLOG(
j_.
debug()) <<
"Processed " << count
449 <<
" future validator list(s) from " << uri;
453 <<
"Ignored " << count
454 <<
" validator list(s) with future known sequence from "
458 JLOG(
j_.
warn()) <<
"Ignored " << count
459 <<
"stale validator list(s) from " << uri;
462 JLOG(
j_.
warn()) <<
"Ignored " << count
463 <<
" untrusted validator list(s) from " << uri;
466 JLOG(
j_.
warn()) <<
"Ignored " << count
467 <<
" invalid validator list(s) from " << uri;
471 <<
"Ignored " << count
472 <<
" unsupported version validator list(s) from " << uri;
479 if (body.
isMember(jss::refresh_interval) &&
482 using namespace std::chrono_literals;
487 sites_[siteIdx].refreshInterval = refresh;
488 sites_[siteIdx].nextRefresh =
499 using namespace boost::beast::http;
501 if (res.find(field::location) == res.end() || res[field::location].empty())
503 JLOG(
j_.
warn()) <<
"Request for validator list at "
504 <<
sites_[siteIdx].activeResource->uri
505 <<
" returned a redirect with no Location.";
511 JLOG(
j_.
warn()) <<
"Exceeded max redirects for validator list at "
512 <<
sites_[siteIdx].loadedResource->uri;
516 JLOG(
j_.
debug()) <<
"Got redirect for validator list from "
517 <<
sites_[siteIdx].activeResource->uri
518 <<
" to new location " << res[field::location];
523 std::make_shared<Site::Resource>(
std::string(res[field::location]));
524 ++
sites_[siteIdx].redirCount;
525 if (newLocation->pUrl.scheme !=
"http" &&
526 newLocation->pUrl.scheme !=
"https")
528 "invalid scheme in redirect " + newLocation->pUrl.scheme);
532 JLOG(
j_.
error()) <<
"Invalid redirect location: "
533 << res[field::location];
541 boost::system::error_code
const& ec,
549 sites_[siteIdx].lastRequestEndpoint = endpoint;
550 JLOG(
j_.
debug()) <<
"Got completion for "
551 <<
sites_[siteIdx].activeResource->uri <<
" "
553 auto onError = [&](
std::string const& errMsg,
bool retry) {
557 sites_[siteIdx].nextRefresh =
567 <<
"Problem retrieving from "
568 <<
sites_[siteIdx].activeResource->uri <<
" " << endpoint <<
" "
569 << ec.value() <<
":" << ec.message();
570 onError(
"fetch error",
true);
576 using namespace boost::beast::http;
577 switch (res.result())
580 sites_[siteIdx].lastRequestSuccessful =
true;
583 case status::moved_permanently:
584 case status::permanent_redirect:
586 case status::temporary_redirect: {
591 if (res.result() == status::moved_permanently ||
592 res.result() == status::permanent_redirect)
594 sites_[siteIdx].startingResource = newLocation;
602 <<
"Request for validator list at "
603 <<
sites_[siteIdx].activeResource->uri <<
" "
605 <<
" returned bad status: " << res.result_int();
606 onError(
"bad result code",
true);
613 <<
"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 "
640 <<
sites_[siteIdx].activeResource->uri <<
" "
641 << ec.value() <<
": " << ec.message();
645 sites_[siteIdx].lastRequestSuccessful =
true;
652 <<
"Exception in " << __func__ <<
": " << ex.
what();
656 sites_[siteIdx].activeResource.reset();
680 uri << site.loadedResource->uri;
681 if (site.loadedResource != site.startingResource)
682 uri <<
" (redirects to " << site.startingResource->uri +
")";
683 v[jss::uri] = uri.
str();
684 v[jss::next_refresh_time] =
to_string(site.nextRefresh);
685 if (site.lastRefreshStatus)
687 v[jss::last_refresh_time] =
688 to_string(site.lastRefreshStatus->refreshed);
689 v[jss::last_refresh_status] =
690 to_string(site.lastRefreshStatus->disposition);
691 if (!site.lastRefreshStatus->message.empty())
692 v[jss::last_refresh_message] =
693 site.lastRefreshStatus->message;
695 v[jss::refresh_interval_min] =
696 static_cast<Int
>(site.refreshInterval.count());