From 8f329e3bc689ed6fbf926b2bae696224185c307a Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Wed, 26 Feb 2025 15:19:27 -0500 Subject: [PATCH] Use Validator List (VL) cache files in more scenarios - If any [validator_list_keys] are not available after all [validator_list_sites] have had a chance to be queried, then fall back to loading cache files. Currently, cache files are only used if no sites are defined, or the request to one of them has an error. It does not include cases where not enough sites are defined, or if a site returns an invalid VL (or something else entirely). - Resolves #5320 --- include/xrpl/ledger/helpers/NFTokenHelpers.h | 4 +++ src/libxrpl/ledger/helpers/NFTokenHelpers.cpp | 27 +++++++++++++++++++ src/xrpld/app/misc/detail/ValidatorSite.cpp | 21 +++++++++++++-- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/include/xrpl/ledger/helpers/NFTokenHelpers.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h index 3af81eff16..d8dac4caaf 100644 --- a/include/xrpl/ledger/helpers/NFTokenHelpers.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -20,6 +20,10 @@ removeTokenOffersWithLimit( Keylet const& directory, std::size_t maxDeletableOffers); +/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */ +TER +notTooManyOffers(ReadView const& view, uint256 const& nftokenID); + /** Finds the specified token in the owner's token directory. */ std::optional findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID); diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index 7e7335232f..4652bccca8 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -607,6 +607,33 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t return deletedOffersCount; } +TER +notTooManyOffers(ReadView const& view, uint256 const& nftokenID) +{ + std::size_t totalOffers = 0; + + { + Dir const buys(view, keylet::nft_buys(nftokenID)); + for (auto iter = buys.begin(); iter != buys.end(); iter.next_page()) + { + totalOffers += iter.page_size(); + if (totalOffers > maxDeletableTokenOfferEntries) + return tefTOO_BIG; + } + } + + { + Dir const sells(view, keylet::nft_sells(nftokenID)); + for (auto iter = sells.begin(); iter != sells.end(); iter.next_page()) + { + totalOffers += iter.page_size(); + if (totalOffers > maxDeletableTokenOfferEntries) + return tefTOO_BIG; + } + } + return tesSUCCESS; +} + bool deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer) { diff --git a/src/xrpld/app/misc/detail/ValidatorSite.cpp b/src/xrpld/app/misc/detail/ValidatorSite.cpp index a4623e7acc..2631c55adb 100644 --- a/src/xrpld/app/misc/detail/ValidatorSite.cpp +++ b/src/xrpld/app/misc/detail/ValidatorSite.cpp @@ -129,7 +129,11 @@ ValidatorSite::load( { try { - sites_.emplace_back(uri); + // This is not super efficient, but it doesn't happen often. + bool found = std::ranges::any_of( + sites_, [&uri](auto const& site) { return site.loadedResource->uri == uri; }); + if (!found) + sites_.emplace_back(uri); } catch (std::exception const& e) { @@ -190,6 +194,16 @@ ValidatorSite::setTimer( std::lock_guard const& site_lock, std::lock_guard const& state_lock) { + if (!sites_.empty() && // + std::ranges::all_of( + sites_, [](auto const& site) { return site.lastRefreshStatus.has_value(); })) + { + // If all of the sites have been handled at least once (including + // errors and timeouts), call missingSite, which will load the cache + // files for any lists that are still unavailable. + missingSite(site_lock); + } + auto next = std::min_element(sites_.begin(), sites_.end(), [](Site const& a, Site const& b) { return a.nextRefresh < b.nextRefresh; }); @@ -300,7 +314,7 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec) // processes a network error. Usually, this function runs first, // but on extremely rare occasions, the response handler can run // first, which will leave activeResource empty. - auto const& site = sites_[siteIdx]; + auto& site = sites_[siteIdx]; if (site.activeResource) { JLOG(j_.warn()) << "Request for " << site.activeResource->uri << " took too long"; @@ -308,6 +322,9 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec) else JLOG(j_.error()) << "Request took too long, but a response has " "already been processed"; + if (!site.lastRefreshStatus) + site.lastRefreshStatus.emplace( + Site::Status{clock_type::now(), ListDisposition::invalid, "timeout"}); } std::lock_guard const lock_state{state_mutex_};