20#include <xrpld/app/misc/ValidatorList.h> 
   21#include <xrpld/app/misc/ValidatorSite.h> 
   22#include <xrpld/app/misc/detail/WorkFile.h> 
   23#include <xrpld/app/misc/detail/WorkPlain.h> 
   24#include <xrpld/app/misc/detail/WorkSSL.h> 
   26#include <xrpl/json/json_reader.h> 
   27#include <xrpl/protocol/digest.h> 
   28#include <xrpl/protocol/jss.h> 
   93    , 
j_{j ? *j : 
app_.logs().journal(
"ValidatorSite")}
 
 
  105    if (
timer_.expiry() > 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_.expiry() == 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;
 
  267    if (resource->pUrl.scheme == 
"https")
 
  271            resource->pUrl.domain,
 
  277            sites_[siteIdx].lastRequestEndpoint,
 
  278            sites_[siteIdx].lastRequestSuccessful,
 
  281    else if (resource->pUrl.scheme == 
"http")
 
  284            resource->pUrl.domain,
 
  288            sites_[siteIdx].lastRequestEndpoint,
 
  289            sites_[siteIdx].lastRequestSuccessful,
 
  294        BOOST_ASSERT(resource->pUrl.scheme == 
"file");
 
  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();
 
  408        JLOG(
j_.
warn()) << 
"Missing fields in JSON response from  " 
  409                        << 
sites_[siteIdx].activeResource->uri;
 
  415        version == body[jss::version].asUInt(),
 
  416        "ripple::ValidatorSite::parseJsonResponse : version match");
 
  417    auto const& uri = 
sites_[siteIdx].activeResource->uri;
 
  429    sites_[siteIdx].lastRefreshStatus.emplace(
 
  432    for (
auto const& [disp, count] : applyResult.dispositions)
 
  437                JLOG(
j_.
debug()) << 
"Applied " << count
 
  438                                 << 
" new validator list(s) from " << uri;
 
  441                JLOG(
j_.
debug()) << 
"Applied " << count
 
  442                                 << 
" expired validator list(s) from " << uri;
 
  446                    << 
"Ignored " << count
 
  447                    << 
" validator list(s) with current sequence from " << uri;
 
  450                JLOG(
j_.
debug()) << 
"Processed " << count
 
  451                                 << 
" future validator list(s) from " << uri;
 
  455                    << 
"Ignored " << count
 
  456                    << 
" validator list(s) with future known sequence from " 
  460                JLOG(
j_.
warn()) << 
"Ignored " << count
 
  461                                << 
"stale validator list(s) from " << uri;
 
  464                JLOG(
j_.
warn()) << 
"Ignored " << count
 
  465                                << 
" untrusted validator list(s) from " << uri;
 
  468                JLOG(
j_.
warn()) << 
"Ignored " << count
 
  469                                << 
" invalid validator list(s) from " << uri;
 
  473                    << 
"Ignored " << count
 
  474                    << 
" unsupported version validator list(s) from " << uri;
 
  481    if (body.
isMember(jss::refresh_interval) &&
 
  484        using namespace std::chrono_literals;
 
  489        sites_[siteIdx].refreshInterval = refresh;
 
  490        sites_[siteIdx].nextRefresh =
 
 
  501    using namespace boost::beast::http;
 
  503    if (res.find(field::location) == res.end() || res[field::location].empty())
 
  505        JLOG(
j_.
warn()) << 
"Request for validator list at " 
  506                        << 
sites_[siteIdx].activeResource->uri
 
  507                        << 
" returned a redirect with no Location.";
 
  513        JLOG(
j_.
warn()) << 
"Exceeded max redirects for validator list at " 
  514                        << 
sites_[siteIdx].loadedResource->uri;
 
  518    JLOG(
j_.
debug()) << 
"Got redirect for validator list from " 
  519                     << 
sites_[siteIdx].activeResource->uri
 
  520                     << 
" to new location " << res[field::location];
 
  526        ++
sites_[siteIdx].redirCount;
 
  527        if (newLocation->pUrl.scheme != 
"http" &&
 
  528            newLocation->pUrl.scheme != 
"https")
 
  530                "invalid scheme in redirect " + newLocation->pUrl.scheme);
 
  534        JLOG(
j_.
error()) << 
"Invalid redirect location: " 
  535                         << res[field::location];
 
 
  543    boost::system::error_code 
const& ec,
 
  551            sites_[siteIdx].lastRequestEndpoint = endpoint;
 
  552        JLOG(
j_.
debug()) << 
"Got completion for " 
  553                         << 
sites_[siteIdx].activeResource->uri << 
" " 
  555        auto onError = [&](
std::string const& errMsg, 
bool retry) {
 
  559                sites_[siteIdx].nextRefresh =
 
  569                << 
"Problem retrieving from " 
  570                << 
sites_[siteIdx].activeResource->uri << 
" " << endpoint << 
" " 
  571                << ec.value() << 
":" << ec.message();
 
  572            onError(
"fetch error", 
true);
 
  578                using namespace boost::beast::http;
 
  579                switch (res.result())
 
  582                        sites_[siteIdx].lastRequestSuccessful = 
true;
 
  585                    case status::moved_permanently:
 
  586                    case status::permanent_redirect:
 
  588                    case status::temporary_redirect: {
 
  593                            "ripple::ValidatorSite::onSiteFetch : non-null " 
  596                        if (res.result() == status::moved_permanently ||
 
  597                            res.result() == status::permanent_redirect)
 
  599                            sites_[siteIdx].startingResource = newLocation;
 
  607                            << 
"Request for validator list at " 
  608                            << 
sites_[siteIdx].activeResource->uri << 
" " 
  610                            << 
" returned bad status: " << res.result_int();
 
  611                        onError(
"bad result code", 
true);
 
  618                    << 
"Exception in " << __func__ << 
": " << ex.
what();
 
  619                onError(ex.
what(), 
false);
 
  622        sites_[siteIdx].activeResource.reset();
 
 
  634    boost::system::error_code 
const& ec,
 
  644                JLOG(
j_.
warn()) << 
"Problem retrieving from " 
  645                                << 
sites_[siteIdx].activeResource->uri << 
" " 
  646                                << ec.value() << 
": " << ec.message();
 
  650            sites_[siteIdx].lastRequestSuccessful = 
true;
 
  657                << 
"Exception in " << __func__ << 
": " << ex.
what();
 
  661        sites_[siteIdx].activeResource.reset();
 
 
  685            uri << site.loadedResource->uri;
 
  686            if (site.loadedResource != site.startingResource)
 
  687                uri << 
" (redirects to " << site.startingResource->uri + 
")";
 
  688            v[jss::uri] = uri.
str();
 
  689            v[jss::next_refresh_time] = 
to_string(site.nextRefresh);
 
  690            if (site.lastRefreshStatus)
 
  692                v[jss::last_refresh_time] =
 
  693                    to_string(site.lastRefreshStatus->refreshed);
 
  694                v[jss::last_refresh_status] =
 
  695                    to_string(site.lastRefreshStatus->disposition);
 
  696                if (!site.lastRefreshStatus->message.empty())
 
  697                    v[jss::last_refresh_message] =
 
  698                        site.lastRefreshStatus->message;
 
  700            v[jss::refresh_interval_min] =
 
  701                static_cast<Int
>(site.refreshInterval.count());
 
 
Unserialize a JSON document into a Value.
 
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
 
Value & append(Value const &value)
Append value to array at the end.
 
std::string asString() const
Returns the unquoted string value.
 
bool isMember(char const *key) const
Return true if the object has a member named key.
 
virtual Config & config()=0
 
virtual Overlay & overlay()=0
 
virtual NetworkOPs & getOPs()=0
 
virtual ValidatorList & validators()=0
 
virtual HashRouter & getHashRouter()=0
 
virtual boost::asio::io_context & getIOContext()=0
 
std::vector< std::string > loadLists()
 
PublisherListStats applyListsAndBroadcast(std::string const &manifest, std::uint32_t version, std::vector< ValidatorBlobInfo > const &blobs, std::string siteUri, uint256 const &hash, Overlay &overlay, HashRouter &hashRouter, NetworkOPs &networkOPs)
Apply multiple published lists of public keys, then broadcast it to all peers that have not seen it o...
 
static std::vector< ValidatorBlobInfo > parseBlobs(std::uint32_t version, Json::Value const &body)
Pull the blob/signature/manifest information out of the appropriate Json body fields depending on the...
 
void start()
Start fetching lists from sites.
 
std::condition_variable cv_
 
std::vector< Site > sites_
 
boost::asio::ip::tcp::endpoint endpoint_type
 
void stop()
Stop fetching lists from sites.
 
Json::Value getJson() const
Return JSON representation of configured validator sites.
 
bool load(std::vector< std::string > const &siteURIs)
Load configured site URIs.
 
void onTextFetch(boost::system::error_code const &ec, std::string const &res, std::size_t siteIdx)
Store latest list fetched from anywhere.
 
std::weak_ptr< detail::Work > work_
 
std::chrono::seconds const requestTimeout_
 
void setTimer(std::lock_guard< std::mutex > const &, std::lock_guard< std::mutex > const &)
Queue next site to be fetched lock over site_mutex_ and state_mutex_ required.
 
ValidatorSite(Application &app, std::optional< beast::Journal > j=std::nullopt, std::chrono::seconds timeout=std::chrono::seconds{20})
 
std::atomic< bool > stopping_
 
void join()
Wait for current fetches from sites to complete.
 
bool missingSite(std::lock_guard< std::mutex > const &)
If no sites are provided, or a site fails to load, get a list of local cache files from the Validator...
 
std::shared_ptr< Site::Resource > processRedirect(detail::response_type &res, std::size_t siteIdx, std::lock_guard< std::mutex > const &)
Interpret a redirect response.
 
void parseJsonResponse(std::string const &res, std::size_t siteIdx, std::lock_guard< std::mutex > const &)
Parse json response from validator list site.
 
void makeRequest(std::shared_ptr< Site::Resource > resource, std::size_t siteIdx, std::lock_guard< std::mutex > const &)
Initiate request to given resource.
 
void onRequestTimeout(std::size_t siteIdx, error_code const &ec)
request took too long
 
std::atomic< bool > pending_
 
boost::system::error_code error_code
 
boost::asio::basic_waitable_timer< clock_type > timer_
 
void onTimer(std::size_t siteIdx, error_code const &ec)
Fetch site whose time has come.
 
void onSiteFetch(boost::system::error_code const &ec, endpoint_type const &endpoint, detail::response_type &&res, std::size_t siteIdx)
Store latest list fetched from site.
 
std::atomic< bool > fetching_
 
@ arrayValue
array value (ordered list)
 
@ objectValue
object value (collection of name/value pairs).
 
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
 
boost::beast::http::response< boost::beast::http::string_body > response_type
 
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
 
@ unsupported_version
List version is not supported.
 
@ stale
Trusted publisher key, but seq is too old.
 
@ untrusted
List signed by untrusted publisher key.
 
@ same_sequence
Same sequence as current list.
 
@ pending
List will be valid in the future.
 
@ known_sequence
Future sequence already seen.
 
@ expired
List is expired, but has the largest non-pending sequence seen so far.
 
@ invalid
Invalid format or signature.
 
bool parseUrl(parsedURL &pUrl, std::string const &strUrl)
 
std::string to_string(base_uint< Bits, Tag > const &a)
 
unsigned short constexpr max_redirects
 
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
 
auto constexpr error_retry_interval
 
auto constexpr default_refresh_interval
 
Resource(std::string uri_)
 
std::shared_ptr< Resource > loadedResource
the original uri as loaded from config
 
std::shared_ptr< Resource > startingResource
the resource to request at <timer> intervals.
 
bool lastRequestSuccessful
 
endpoint_type lastRequestEndpoint
 
std::chrono::minutes refreshInterval
 
unsigned short redirCount
 
clock_type::time_point nextRefresh
 
std::optional< std::uint16_t > port