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}
85 , lastRequestEndpoint{}
86 , lastRequestSuccessful{
false}
95 , j_{j ? *j : app_.logs().journal(
"ValidatorSite")}
96 , timer_{app_.getIOService()}
100 , requestTimeout_{timeout}
107 if (
timer_.expires_at() > clock_type::time_point{})
132 if (siteURIs.
empty())
137 JLOG(
j_.
debug()) <<
"Loading configured validator list sites";
141 for (
auto const& uri : siteURIs)
150 <<
"Invalid validator site uri: " << uri <<
": " << e.
what();
155 JLOG(
j_.
debug()) <<
"Loaded " << siteURIs.size() <<
" sites";
164 if (
timer_.expires_at() == clock_type::time_point{})
183 if (
auto sp =
work_.lock())
193 catch (boost::system::system_error
const&)
208 return a.nextRefresh < b.nextRefresh;
215 timer_.expires_at(next->nextRefresh);
217 timer_.async_wait([
this, idx](boost::system::error_code
const& ec) {
230 sites_[siteIdx].activeResource = resource;
232 auto timeoutCancel = [
this]() {
240 catch (boost::system::system_error
const&)
244 auto onFetch = [
this, siteIdx, timeoutCancel](
249 onSiteFetch(err, endpoint, std::move(resp), siteIdx);
252 auto onFetchFile = [
this, siteIdx, timeoutCancel](
258 JLOG(
j_.
debug()) <<
"Starting request for " << resource->
uri;
263 sp = std::make_shared<detail::WorkSSL>(
270 sites_[siteIdx].lastRequestEndpoint,
271 sites_[siteIdx].lastRequestSuccessful,
276 sp = std::make_shared<detail::WorkPlain>(
281 sites_[siteIdx].lastRequestEndpoint,
282 sites_[siteIdx].lastRequestSuccessful,
287 BOOST_ASSERT(resource->
pUrl.
scheme ==
"file");
288 sp = std::make_shared<detail::WorkFile>(
292 sites_[siteIdx].lastRequestSuccessful =
false;
299 timer_.async_wait([
this, siteIdx](boost::system::error_code
const& ec) {
312 JLOG(
j_.
warn()) <<
"Request for " <<
sites_[siteIdx].activeResource->uri
317 if (
auto sp =
work_.lock())
328 if (ec != boost::asio::error::operation_aborted)
336 sites_[siteIdx].nextRefresh =
338 sites_[siteIdx].redirCount = 0;
345 boost::system::error_code{-1, boost::system::generic_category()},
362 JLOG(
j_.
warn()) <<
"Unable to parse JSON response from "
363 <<
sites_[siteIdx].activeResource->uri;
371 !body[
"version"].
isInt())
373 JLOG(
j_.
warn()) <<
"Missing fields in JSON response from "
374 <<
sites_[siteIdx].activeResource->uri;
379 auto const blob = body[
"blob"].
asString();
380 auto const signature = body[
"signature"].
asString();
381 auto const version = body[
"version"].
asUInt();
382 auto const& uri =
sites_[siteIdx].activeResource->uri;
395 sites_[siteIdx].lastRefreshStatus.emplace(
401 JLOG(
j_.
debug()) <<
"Applied new validator list from " << uri;
405 <<
"Validator list with current sequence from " << uri;
408 JLOG(
j_.
warn()) <<
"Stale validator list from " << uri;
411 JLOG(
j_.
warn()) <<
"Untrusted validator list from " << uri;
414 JLOG(
j_.
warn()) <<
"Invalid validator list from " << uri;
418 <<
"Unsupported version validator list from " << uri;
424 if (body.
isMember(
"refresh_interval") &&
427 using namespace std::chrono_literals;
430 sites_[siteIdx].refreshInterval = refresh;
431 sites_[siteIdx].nextRefresh =
442 using namespace boost::beast::http;
444 if (res.find(field::location) == res.end() || res[field::location].empty())
446 JLOG(
j_.
warn()) <<
"Request for validator list at "
447 <<
sites_[siteIdx].activeResource->uri
448 <<
" returned a redirect with no Location.";
454 JLOG(
j_.
warn()) <<
"Exceeded max redirects for validator list at "
455 <<
sites_[siteIdx].loadedResource->uri;
459 JLOG(
j_.
debug()) <<
"Got redirect for validator list from "
460 <<
sites_[siteIdx].activeResource->uri
461 <<
" to new location " << res[field::location];
466 std::make_shared<Site::Resource>(
std::string(res[field::location]));
467 ++
sites_[siteIdx].redirCount;
468 if (newLocation->pUrl.scheme !=
"http" &&
469 newLocation->pUrl.scheme !=
"https")
471 "invalid scheme in redirect " + newLocation->pUrl.scheme);
475 JLOG(
j_.
error()) <<
"Invalid redirect location: "
476 << res[field::location];
484 boost::system::error_code
const& ec,
492 sites_[siteIdx].lastRequestEndpoint = endpoint;
493 JLOG(
j_.
debug()) <<
"Got completion for "
494 <<
sites_[siteIdx].activeResource->uri <<
" "
496 auto onError = [&](
std::string const& errMsg,
bool retry) {
500 sites_[siteIdx].nextRefresh =
510 <<
"Problem retrieving from "
511 <<
sites_[siteIdx].activeResource->uri <<
" " << endpoint <<
" "
512 << ec.value() <<
":" << ec.message();
513 onError(
"fetch error",
true);
519 using namespace boost::beast::http;
520 switch (res.result())
523 sites_[siteIdx].lastRequestSuccessful =
true;
526 case status::moved_permanently:
527 case status::permanent_redirect:
529 case status::temporary_redirect: {
534 if (res.result() == status::moved_permanently ||
535 res.result() == status::permanent_redirect)
537 sites_[siteIdx].startingResource = newLocation;
545 <<
"Request for validator list at "
546 <<
sites_[siteIdx].activeResource->uri <<
" "
548 <<
" returned bad status: " << res.result_int();
549 onError(
"bad result code",
true);
555 onError(ex.
what(),
false);
558 sites_[siteIdx].activeResource.reset();
570 boost::system::error_code
const& ec,
580 JLOG(
j_.
warn()) <<
"Problem retrieving from "
581 <<
sites_[siteIdx].activeResource->uri <<
" "
582 << ec.value() <<
": " << ec.message();
586 sites_[siteIdx].lastRequestSuccessful =
true;
595 sites_[siteIdx].activeResource.reset();
619 uri << site.loadedResource->uri;
620 if (site.loadedResource != site.startingResource)
621 uri <<
" (redirects to " << site.startingResource->uri +
")";
622 v[jss::uri] = uri.
str();
623 v[jss::next_refresh_time] =
to_string(site.nextRefresh);
624 if (site.lastRefreshStatus)
626 v[jss::last_refresh_time] =
627 to_string(site.lastRefreshStatus->refreshed);
628 v[jss::last_refresh_status] =
629 to_string(site.lastRefreshStatus->disposition);
630 if (!site.lastRefreshStatus->message.empty())
631 v[jss::last_refresh_message] =
632 site.lastRefreshStatus->message;
634 v[jss::refresh_interval_min] =
635 static_cast<Int
>(site.refreshInterval.count());