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/base64.h>
26 #include <ripple/basics/Slice.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>
41 : uri {std::move(uri_)}
46 if (pUrl.scheme ==
"file")
48 if (!pUrl.domain.empty())
53 if (pUrl.path[0] ==
'/')
54 pUrl.path = pUrl.path.substr(1);
57 if (pUrl.path.empty())
60 else if (pUrl.scheme ==
"http")
62 if (pUrl.domain.empty())
68 else if (pUrl.scheme ==
"https")
70 if (pUrl.domain.empty())
82 , startingResource {loadedResource}
91 boost::optional<beast::Journal> j,
94 , j_ {j ? *j : app_.logs().journal(
"ValidatorSite") }
95 , timer_ {app_.getIOService()}
99 , requestTimeout_ {timeout}
106 if (
timer_.expires_at() > clock_type::time_point{})
132 if (siteURIs.
empty())
138 "Loading configured validator list sites";
142 for (
auto const& uri : siteURIs)
146 sites_.emplace_back (uri);
151 "Invalid validator site uri: " << uri <<
158 "Loaded " << siteURIs.size() <<
" sites";
167 if (
timer_.expires_at() == clock_type::time_point{})
186 if(
auto sp =
work_.lock())
196 catch (boost::system::system_error
const&)
212 return a.nextRefresh < b.nextRefresh;
215 if (next !=
sites_.end ())
219 timer_.expires_at (next->nextRefresh);
221 timer_.async_wait ([
this, idx] (boost::system::error_code
const& ec)
235 sites_[siteIdx].activeResource = resource;
247 catch (boost::system::system_error
const&)
252 [
this, siteIdx, timeoutCancel] (
260 [
this, siteIdx, timeoutCancel] (
267 JLOG (
j_.
debug()) <<
"Starting request for " << resource->
uri;
272 sp = std::make_shared<detail::WorkSSL>(
283 sp = std::make_shared<detail::WorkPlain>(
292 BOOST_ASSERT(resource->
pUrl.
scheme ==
"file");
293 sp = std::make_shared<detail::WorkFile>(
305 timer_.async_wait ([
this, siteIdx] (boost::system::error_code
const& ec)
322 "Request for " <<
sites_[siteIdx].activeResource->uri <<
327 if(
auto sp =
work_.lock())
340 if (ec != boost::asio::error::operation_aborted)
348 sites_[siteIdx].nextRefresh =
350 sites_[siteIdx].redirCount = 0;
357 boost::system::error_code {-1, boost::system::generic_category()},
374 "Unable to parse JSON response from " <<
375 sites_[siteIdx].activeResource->uri;
386 "Missing fields in JSON response from " <<
387 sites_[siteIdx].activeResource->uri;
392 auto const blob = body[
"blob"].
asString ();
393 auto const signature = body[
"signature"].
asString();
394 auto const version = body[
"version"].
asUInt();
395 auto const& uri =
sites_[siteIdx].activeResource->uri;
408 sites_[siteIdx].lastRefreshStatus.emplace(
415 "Applied new validator list from " <<
420 "Validator list with current sequence from " <<
425 "Stale validator list from " <<
430 "Untrusted validator list from " <<
435 "Invalid validator list from " <<
440 "Unsupported version validator list from " <<
447 if (body.
isMember (
"refresh_interval") &&
450 using namespace std::chrono_literals;
452 boost::algorithm::clamp(
456 sites_[siteIdx].refreshInterval = refresh;
457 sites_[siteIdx].nextRefresh =
468 using namespace boost::beast::http;
470 if (res.find(field::location) == res.end() ||
471 res[field::location].empty())
474 "Request for validator list at " <<
475 sites_[siteIdx].activeResource->uri <<
476 " returned a redirect with no Location.";
483 "Exceeded max redirects for validator list at " <<
484 sites_[siteIdx].loadedResource->uri ;
489 "Got redirect for validator list from " <<
490 sites_[siteIdx].activeResource->uri <<
491 " to new location " << res[field::location];
495 newLocation = std::make_shared<Site::Resource>(
497 ++
sites_[siteIdx].redirCount;
498 if (newLocation->pUrl.scheme !=
"http" &&
499 newLocation->pUrl.scheme !=
"https")
501 newLocation->pUrl.scheme);
506 "Invalid redirect location: " << res[field::location];
514 boost::system::error_code
const& ec,
520 JLOG (
j_.
debug()) <<
"Got completion for "
521 <<
sites_[siteIdx].activeResource->uri;
522 auto onError = [&](
std::string const& errMsg,
bool retry)
524 sites_[siteIdx].lastRefreshStatus.emplace(
529 sites_[siteIdx].nextRefresh =
539 "Problem retrieving from " <<
540 sites_[siteIdx].activeResource->uri <<
545 onError(
"fetch error",
true);
551 using namespace boost::beast::http;
552 switch (res.result())
557 case status::moved_permanently :
558 case status::permanent_redirect :
560 case status::temporary_redirect :
566 if (res.result() == status::moved_permanently ||
567 res.result() == status::permanent_redirect)
569 sites_[siteIdx].startingResource = newLocation;
578 "Request for validator list at " <<
579 sites_[siteIdx].activeResource->uri <<
580 " returned bad status: " <<
582 onError(
"bad result code",
true);
588 onError(ex.
what(),
false);
591 sites_[siteIdx].activeResource.reset();
603 boost::system::error_code
const& ec,
614 "Problem retrieving from " <<
615 sites_[siteIdx].activeResource->uri <<
627 sites_[siteIdx].lastRefreshStatus.emplace(
632 sites_[siteIdx].activeResource.reset();
656 uri << site.loadedResource->uri;
657 if (site.loadedResource != site.startingResource)
658 uri <<
" (redirects to " << site.startingResource->uri +
")";
659 v[jss::uri] = uri.
str();
660 v[jss::next_refresh_time] =
to_string(site.nextRefresh);
661 if (site.lastRefreshStatus)
663 v[jss::last_refresh_time] =
664 to_string(site.lastRefreshStatus->refreshed);
665 v[jss::last_refresh_status] =
666 to_string(site.lastRefreshStatus->disposition);
667 if (! site.lastRefreshStatus->message.empty())
668 v[jss::last_refresh_message] =
669 site.lastRefreshStatus->message;
671 v[jss::refresh_interval_min] =
672 static_cast<Int
>(site.refreshInterval.count());