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>
30 #include <boost/algorithm/clamp.hpp>
31 #include <boost/regex.hpp>
45 if (pUrl.scheme ==
"file")
47 if (!pUrl.domain.empty())
52 if (pUrl.path[0] ==
'/')
53 pUrl.path = pUrl.path.substr(1);
56 if (pUrl.path.empty())
59 else if (pUrl.scheme ==
"http")
61 if (pUrl.domain.empty())
67 else if (pUrl.scheme ==
"https")
69 if (pUrl.domain.empty())
81 , startingResource{loadedResource}
90 boost::optional<beast::Journal> j,
93 , j_{j ? *j : app_.logs().journal(
"ValidatorSite")}
94 , timer_{app_.getIOService()}
98 , requestTimeout_{timeout}
105 if (
timer_.expires_at() > clock_type::time_point{})
130 if (siteURIs.
empty())
135 JLOG(
j_.
debug()) <<
"Loading configured validator list sites";
139 for (
auto const& uri : siteURIs)
148 <<
"Invalid validator site uri: " << uri <<
": " << e.
what();
153 JLOG(
j_.
debug()) <<
"Loaded " << siteURIs.size() <<
" sites";
162 if (
timer_.expires_at() == clock_type::time_point{})
181 if (
auto sp =
work_.lock())
191 catch (boost::system::system_error
const&)
206 return a.nextRefresh < b.nextRefresh;
213 timer_.expires_at(next->nextRefresh);
215 timer_.async_wait([
this, idx](boost::system::error_code
const& ec) {
228 sites_[siteIdx].activeResource = resource;
230 auto timeoutCancel = [
this]() {
238 catch (boost::system::system_error
const&)
242 auto onFetch = [
this, siteIdx, timeoutCancel](
248 auto onFetchFile = [
this, siteIdx, timeoutCancel](
254 JLOG(
j_.
debug()) <<
"Starting request for " << resource->
uri;
259 sp = std::make_shared<detail::WorkSSL>(
270 sp = std::make_shared<detail::WorkPlain>(
279 BOOST_ASSERT(resource->
pUrl.
scheme ==
"file");
280 sp = std::make_shared<detail::WorkFile>(
290 timer_.async_wait([
this, siteIdx](boost::system::error_code
const& ec) {
303 JLOG(
j_.
warn()) <<
"Request for " <<
sites_[siteIdx].activeResource->uri
308 if (
auto sp =
work_.lock())
319 if (ec != boost::asio::error::operation_aborted)
327 sites_[siteIdx].nextRefresh =
329 sites_[siteIdx].redirCount = 0;
336 boost::system::error_code{-1, boost::system::generic_category()},
352 JLOG(
j_.
warn()) <<
"Unable to parse JSON response from "
353 <<
sites_[siteIdx].activeResource->uri;
361 !body[
"version"].
isInt())
363 JLOG(
j_.
warn()) <<
"Missing fields in JSON response from "
364 <<
sites_[siteIdx].activeResource->uri;
369 auto const blob = body[
"blob"].
asString();
370 auto const signature = body[
"signature"].
asString();
371 auto const version = body[
"version"].
asUInt();
372 auto const& uri =
sites_[siteIdx].activeResource->uri;
385 sites_[siteIdx].lastRefreshStatus.emplace(
391 JLOG(
j_.
debug()) <<
"Applied new validator list from " << uri;
395 <<
"Validator list with current sequence from " << uri;
398 JLOG(
j_.
warn()) <<
"Stale validator list from " << uri;
401 JLOG(
j_.
warn()) <<
"Untrusted validator list from " << uri;
404 JLOG(
j_.
warn()) <<
"Invalid validator list from " << uri;
408 <<
"Unsupported version validator list from " << uri;
414 if (body.
isMember(
"refresh_interval") &&
417 using namespace std::chrono_literals;
420 sites_[siteIdx].refreshInterval = refresh;
421 sites_[siteIdx].nextRefresh =
432 using namespace boost::beast::http;
434 if (res.find(field::location) == res.end() || res[field::location].empty())
436 JLOG(
j_.
warn()) <<
"Request for validator list at "
437 <<
sites_[siteIdx].activeResource->uri
438 <<
" returned a redirect with no Location.";
444 JLOG(
j_.
warn()) <<
"Exceeded max redirects for validator list at "
445 <<
sites_[siteIdx].loadedResource->uri;
449 JLOG(
j_.
debug()) <<
"Got redirect for validator list from "
450 <<
sites_[siteIdx].activeResource->uri
451 <<
" to new location " << res[field::location];
456 std::make_shared<Site::Resource>(
std::string(res[field::location]));
457 ++
sites_[siteIdx].redirCount;
458 if (newLocation->pUrl.scheme !=
"http" &&
459 newLocation->pUrl.scheme !=
"https")
461 "invalid scheme in redirect " + newLocation->pUrl.scheme);
465 JLOG(
j_.
error()) <<
"Invalid redirect location: "
466 << res[field::location];
474 boost::system::error_code
const& ec,
480 JLOG(
j_.
debug()) <<
"Got completion for "
481 <<
sites_[siteIdx].activeResource->uri;
482 auto onError = [&](
std::string const& errMsg,
bool retry) {
486 sites_[siteIdx].nextRefresh =
495 JLOG(
j_.
warn()) <<
"Problem retrieving from "
496 <<
sites_[siteIdx].activeResource->uri <<
" "
497 << ec.value() <<
":" << ec.message();
498 onError(
"fetch error",
true);
504 using namespace boost::beast::http;
505 switch (res.result())
510 case status::moved_permanently:
511 case status::permanent_redirect:
513 case status::temporary_redirect: {
518 if (res.result() == status::moved_permanently ||
519 res.result() == status::permanent_redirect)
521 sites_[siteIdx].startingResource = newLocation;
529 <<
"Request for validator list at "
530 <<
sites_[siteIdx].activeResource->uri
531 <<
" returned bad status: " << res.result_int();
532 onError(
"bad result code",
true);
538 onError(ex.
what(),
false);
541 sites_[siteIdx].activeResource.reset();
553 boost::system::error_code
const& ec,
563 JLOG(
j_.
warn()) <<
"Problem retrieving from "
564 <<
sites_[siteIdx].activeResource->uri <<
" "
565 << ec.value() <<
": " << ec.message();
576 sites_[siteIdx].activeResource.reset();
600 uri << site.loadedResource->uri;
601 if (site.loadedResource != site.startingResource)
602 uri <<
" (redirects to " << site.startingResource->uri +
")";
603 v[jss::uri] = uri.
str();
604 v[jss::next_refresh_time] =
to_string(site.nextRefresh);
605 if (site.lastRefreshStatus)
607 v[jss::last_refresh_time] =
608 to_string(site.lastRefreshStatus->refreshed);
609 v[jss::last_refresh_status] =
610 to_string(site.lastRefreshStatus->disposition);
611 if (!site.lastRefreshStatus->message.empty())
612 v[jss::last_refresh_message] =
613 site.lastRefreshStatus->message;
615 v[jss::refresh_interval_min] =
616 static_cast<Int
>(site.refreshInterval.count());