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>
44 if (pUrl.scheme ==
"file")
46 if (!pUrl.domain.empty())
51 if (pUrl.path[0] ==
'/')
52 pUrl.path = pUrl.path.substr(1);
55 if (pUrl.path.empty())
58 else if (pUrl.scheme ==
"http")
60 if (pUrl.domain.empty())
66 else if (pUrl.scheme ==
"https")
68 if (pUrl.domain.empty())
80 , startingResource{loadedResource}
84 , lastRequestEndpoint{}
85 , lastRequestSuccessful{
false}
94 , j_{j ? *j : app_.logs().journal(
"ValidatorSite")}
95 , timer_{app_.getIOService()}
99 , requestTimeout_{timeout}
106 if (
timer_.expires_at() > clock_type::time_point{})
131 if (siteURIs.
empty())
136 JLOG(
j_.
debug()) <<
"Loading configured validator list sites";
140 for (
auto const& uri : siteURIs)
149 <<
"Invalid validator site uri: " << uri <<
": " << e.
what();
154 JLOG(
j_.
debug()) <<
"Loaded " << siteURIs.size() <<
" sites";
163 if (
timer_.expires_at() == clock_type::time_point{})
182 if (
auto sp =
work_.lock())
192 catch (boost::system::system_error
const&)
207 return a.nextRefresh < b.nextRefresh;
214 timer_.expires_at(next->nextRefresh);
216 timer_.async_wait([
this, idx](boost::system::error_code
const& ec) {
229 sites_[siteIdx].activeResource = resource;
231 auto timeoutCancel = [
this]() {
239 catch (boost::system::system_error
const&)
243 auto onFetch = [
this, siteIdx, timeoutCancel](
248 onSiteFetch(err, endpoint, std::move(resp), siteIdx);
251 auto onFetchFile = [
this, siteIdx, timeoutCancel](
257 JLOG(
j_.
debug()) <<
"Starting request for " << resource->
uri;
262 sp = std::make_shared<detail::WorkSSL>(
269 sites_[siteIdx].lastRequestEndpoint,
270 sites_[siteIdx].lastRequestSuccessful,
275 sp = std::make_shared<detail::WorkPlain>(
280 sites_[siteIdx].lastRequestEndpoint,
281 sites_[siteIdx].lastRequestSuccessful,
286 BOOST_ASSERT(resource->
pUrl.
scheme ==
"file");
287 sp = std::make_shared<detail::WorkFile>(
291 sites_[siteIdx].lastRequestSuccessful =
false;
298 timer_.async_wait([
this, siteIdx](boost::system::error_code
const& ec) {
311 JLOG(
j_.
warn()) <<
"Request for " <<
sites_[siteIdx].activeResource->uri
316 if (
auto sp =
work_.lock())
327 if (ec != boost::asio::error::operation_aborted)
335 sites_[siteIdx].nextRefresh =
337 sites_[siteIdx].redirCount = 0;
344 boost::system::error_code{-1, boost::system::generic_category()},
362 JLOG(
j_.
warn()) <<
"Unable to parse JSON response from "
363 <<
sites_[siteIdx].activeResource->uri;
369 auto const [valid, version, blobs] = [&body]() {
373 body[jss::version].
isInt();
379 version = body[jss::version].
asUInt();
381 valid = !blobs.
empty();
388 JLOG(
j_.
warn()) <<
"Missing fields in JSON response from "
389 <<
sites_[siteIdx].activeResource->uri;
394 assert(version == body[jss::version].asUInt());
395 auto const& uri =
sites_[siteIdx].activeResource->uri;
407 sites_[siteIdx].lastRefreshStatus.emplace(
410 for (
auto const& [disp, count] : applyResult.dispositions)
415 JLOG(
j_.
debug()) <<
"Applied " << count
416 <<
" new validator list(s) from " << uri;
419 JLOG(
j_.
debug()) <<
"Applied " << count
420 <<
" expired validator list(s) from " << uri;
424 <<
"Ignored " << count
425 <<
" validator list(s) with current sequence from " << uri;
428 JLOG(
j_.
debug()) <<
"Processed " << count
429 <<
" future validator list(s) from " << uri;
433 <<
"Ignored " << count
434 <<
" validator list(s) with future known sequence from "
438 JLOG(
j_.
warn()) <<
"Ignored " << count
439 <<
"stale validator list(s) from " << uri;
442 JLOG(
j_.
warn()) <<
"Ignored " << count
443 <<
" untrusted validator list(s) from " << uri;
446 JLOG(
j_.
warn()) <<
"Ignored " << count
447 <<
" invalid validator list(s) from " << uri;
451 <<
"Ignored " << count
452 <<
" unsupported version validator list(s) from " << uri;
459 if (body.
isMember(jss::refresh_interval) &&
462 using namespace std::chrono_literals;
467 sites_[siteIdx].refreshInterval = refresh;
468 sites_[siteIdx].nextRefresh =
479 using namespace boost::beast::http;
481 if (res.find(field::location) == res.end() || res[field::location].empty())
483 JLOG(
j_.
warn()) <<
"Request for validator list at "
484 <<
sites_[siteIdx].activeResource->uri
485 <<
" returned a redirect with no Location.";
491 JLOG(
j_.
warn()) <<
"Exceeded max redirects for validator list at "
492 <<
sites_[siteIdx].loadedResource->uri;
496 JLOG(
j_.
debug()) <<
"Got redirect for validator list from "
497 <<
sites_[siteIdx].activeResource->uri
498 <<
" to new location " << res[field::location];
503 std::make_shared<Site::Resource>(
std::string(res[field::location]));
504 ++
sites_[siteIdx].redirCount;
505 if (newLocation->pUrl.scheme !=
"http" &&
506 newLocation->pUrl.scheme !=
"https")
508 "invalid scheme in redirect " + newLocation->pUrl.scheme);
512 JLOG(
j_.
error()) <<
"Invalid redirect location: "
513 << res[field::location];
521 boost::system::error_code
const& ec,
529 sites_[siteIdx].lastRequestEndpoint = endpoint;
530 JLOG(
j_.
debug()) <<
"Got completion for "
531 <<
sites_[siteIdx].activeResource->uri <<
" "
533 auto onError = [&](
std::string const& errMsg,
bool retry) {
537 sites_[siteIdx].nextRefresh =
547 <<
"Problem retrieving from "
548 <<
sites_[siteIdx].activeResource->uri <<
" " << endpoint <<
" "
549 << ec.value() <<
":" << ec.message();
550 onError(
"fetch error",
true);
556 using namespace boost::beast::http;
557 switch (res.result())
560 sites_[siteIdx].lastRequestSuccessful =
true;
563 case status::moved_permanently:
564 case status::permanent_redirect:
566 case status::temporary_redirect: {
571 if (res.result() == status::moved_permanently ||
572 res.result() == status::permanent_redirect)
574 sites_[siteIdx].startingResource = newLocation;
582 <<
"Request for validator list at "
583 <<
sites_[siteIdx].activeResource->uri <<
" "
585 <<
" returned bad status: " << res.result_int();
586 onError(
"bad result code",
true);
592 onError(ex.
what(),
false);
595 sites_[siteIdx].activeResource.reset();
607 boost::system::error_code
const& ec,
617 JLOG(
j_.
warn()) <<
"Problem retrieving from "
618 <<
sites_[siteIdx].activeResource->uri <<
" "
619 << ec.value() <<
": " << ec.message();
623 sites_[siteIdx].lastRequestSuccessful =
true;
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());