Compare commits

...

39 Commits

Author SHA1 Message Date
Ed Hennis
5d2be06748 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 16:53:46 -04:00
Vito Tumas
93ac1aa7aa fix: Disable unnecessary sanity-check in VaultDeposit (#7288) 2026-05-19 16:38:50 +00:00
dependabot[bot]
d9a3af8207 ci: [DEPENDABOT] bump actions/upload-artifact from 7.0.0 to 7.0.1 (#7286)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 16:35:38 +00:00
Ed Hennis
2f5b17c2cb Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 10:15:25 -04:00
Ayaz Salikhov
8d1083e5ea ci: Only run reusable package in public repos (#7293) 2026-05-19 13:15:11 +00:00
Ed Hennis
a963e4a389 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 05:16:02 -04:00
Fomo
1e45d363c5 fix: Set default peering port to 2459 (#6848)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-05-19 06:05:47 +00:00
Ed Hennis
962f3ee744 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-15 21:43:29 -04:00
Ed Hennis
2a7499a415 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-14 20:39:19 -04:00
Ed Hennis
ae8c44b606 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-13 22:32:02 -04:00
Ed Hennis
1edd6fe4bf Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-13 12:03:49 -04:00
Ed Hennis
2edf310a6f Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-12 19:18:57 -04:00
Ed Hennis
7af68be818 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-12 16:26:19 -04:00
Ed Hennis
19fc5c48dd Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-11 13:34:22 -04:00
Ed Hennis
2f43a1901d Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 18:10:13 -04:00
Ed Hennis
6a0440bf6d Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 14:18:39 -04:00
Ed Hennis
cf398d05aa Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 13:28:42 -04:00
Ed Hennis
59a8e30a3e Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-06 22:34:33 -04:00
Ed Hennis
8160921783 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-06 14:18:12 -04:00
Ed Hennis
6dc01196b4 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 16:46:54 -04:00
Ed Hennis
cdbbbdd27f Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 10:50:44 -04:00
Ed Hennis
3d30c09031 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 00:14:58 -04:00
Ed Hennis
e59ca23487 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-01 13:59:15 -04:00
Ed Hennis
72c700c3e0 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-28 16:23:38 -04:00
Ed Hennis
4589fcbcfc Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-25 14:45:54 -04:00
Ed Hennis
86d840f53d Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-23 15:56:11 -04:00
Ed Hennis
741b61cdf3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 23:40:28 -04:00
Ed Hennis
6bb0989c9f Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 14:49:12 -04:00
Ed Hennis
9120506613 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 13:10:44 -04:00
Ed Hennis
3b3de96bd4 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-21 18:48:39 -04:00
Ed Hennis
c9ab6ab25f Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop:
  chore: Remove empty Taker.h (6984)
  chore: Enable clang-tidy modernize checks (6975)
  ci: Upload clang-tidy git diff (6983)
  fix: Add rounding to Vault invariants (6217) (6955)
2026-04-21 18:46:58 -04:00
Ed Hennis
fb0605cfd3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 17:49:47 -04:00
Ed Hennis
156553bb5e Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 15:45:04 -04:00
Ed Hennis
781b56849b Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 11:39:06 -04:00
Ed Hennis
278c02bebb Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-17 18:11:31 -04:00
Ed Hennis
1d6fedf9a2 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-16 13:44:37 -04:00
Ed Hennis
2e8de499aa Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 19:06:29 -04:00
Ed Hennis
0bce3639a6 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 14:28:55 -04:00
Ed Hennis
8f329e3bc6 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
2026-04-13 19:46:06 -04:00
7 changed files with 154 additions and 21 deletions

View File

@@ -58,6 +58,7 @@ jobs:
package:
needs: [generate-matrix, generate-version]
if: ${{ github.event.repository.visibility == 'public' }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
@@ -88,8 +89,7 @@ jobs:
run: ./package/build_pkg.sh
- name: Upload package artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: ${{ github.event.repository.visibility == 'public' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
path: |

View File

@@ -1466,10 +1466,7 @@ admin = 127.0.0.1
protocol = http
[port_peer]
# Many servers still use the legacy port of 51235, so for backward-compatibility
# we maintain that port number here. However, for new servers we recommend
# changing this to the default port of 2459.
port = 51235
port = 2459
ip = 0.0.0.0
# alternatively, to accept connections on IPv4 + IPv6, use:
#ip = ::

View File

@@ -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<STObject>
findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID);

View File

@@ -622,6 +622,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<SLE> const& offer)
{

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -252,19 +253,26 @@ VaultDeposit::doApply()
!isTesSuccess(ter))
return ter;
// Sanity check
if (accountHolds(
view(),
accountID_,
assetsDeposited.asset(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j_) < beast::kZero)
// This check is wrong. Disable it with fixCleanup3_2_0.
// For XRP and MPT the predicate is structurally unsatisfiable: xrpLiquid clamps at zero, and
// MPT balances are unsigned. For IOUs it only fires when the deposit drove the depositor's
// trust line into debt the exact case preclaim authorizes via SpendableHandling::FullBalance.
// The check thus converts a preclaim- authorized deposit into tefINTERNAL after the asset
// transfer.
if (!view().rules().enabled(fixCleanup3_2_0))
{
// LCOV_EXCL_START
JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
return tefINTERNAL;
// LCOV_EXCL_STOP
// Sanity check
if (accountHolds(
view(),
accountID_,
assetsDeposited.asset(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j_) < beast::kZero)
{
JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
return tefINTERNAL;
}
}
// Transfer shares from vault to depositor.

View File

@@ -6140,10 +6140,90 @@ class Vault_test : public beast::unit_test::Suite
runTest(amendments);
}
// VaultDeposit::preclaim uses accountHolds(..., SpendableHandling::
// shFULL_BALANCE), which for an IOU asset adds the counterparty's
// LowLimit/HighLimit to the depositor's raw balance (TokenHelpers.cpp:
// getTrustLineBalance with includeOppositeLimit=true). When the
// depositor's raw balance < deposit amount but raw + opposite limit >=
// amount, preclaim is satisfied. doApply then calls
// directSendNoFeeIOU, which unconditionally subtracts saAmount from
// saBalance — driving the trust line negative — and returns tesSUCCESS.
// The post-send sanity check uses the default shSIMPLE_BALANCE (no
// opposite-limit add), sees a negative balance, and returns tefINTERNAL.
void
testVaultDepositNegativeBalanceFromOppositeLimit()
{
auto runTest = [&](FeatureBitset f, TER expected) {
using namespace test::jtx;
using namespace std::literals;
Env env{*this, f};
Account const gw{"gateway"};
Account const owner{"owner"};
Account const depositor{"depositor"};
env.fund(XRP(10000), gw, owner, depositor);
env.close();
// Gateway with DefaultRipple so vault creation on its IOU works.
env(fset(gw, asfDefaultRipple));
env.close();
// Depositor opens a trust line to gateway and receives a small
// balance.
PrettyAsset const usd = gw["USD"];
env.trust(usd(1000), depositor);
env(pay(gw, depositor, usd(100))); // raw trust-line balance: 100
env.close();
// Key precondition: gateway sets a non-zero limit on the same
// RippleState — the "opposite field" from depositor's perspective.
// This is what inflates shFULL_BALANCE in preclaim above the raw
// balance.
env(trust(gw, depositor["USD"](1000)));
env.close();
// Create the IOU vault.
Vault const vault{env};
auto [vaultTx, keylet] = vault.create({.owner = owner, .asset = usd});
env(vaultTx);
env.close();
// Submit a deposit of 500 USD:
// - raw balance: 100 USD
// - opposite limit (gw's side): 1000 USD
// - preclaim sees 100 + 1000 = 1100, passes (>= 500)
// - doApply transfers 500, depositor's trust-line balance
// becomes -400
// - sanity check at VaultDeposit.cpp:256 fires
// - tx returns tefINTERNAL (BUG — should be tesSUCCESS.
auto depositTx =
vault.deposit({.depositor = depositor, .id = keylet.key, .amount = usd(500)});
env(depositTx, Ter(expected));
env.close();
};
{
testcase(
"IOU vault deposit exceeding depositor's balance but "
"within counterparty's trust limit, pre-fixCleanup3_2_0 "
"(tefINTERNAL)");
runTest(test::jtx::testableAmendments() - fixCleanup3_2_0, tefINTERNAL);
}
{
testcase(
"IOU vault deposit exceeding depositor's balance but "
"within counterparty's trust limit, post-fixCleanup3_2_0 "
"(tesSUCCESS)");
runTest(test::jtx::testableAmendments(), tesSUCCESS);
}
}
public:
void
run() override
{
testVaultDepositNegativeBalanceFromOppositeLimit();
testSequences();
testPreflight();
testCreateFailXRP();

View File

@@ -161,7 +161,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)
{
@@ -222,7 +226,17 @@ ValidatorSite::setTimer(
std::scoped_lock<std::mutex> const& siteLock,
std::scoped_lock<std::mutex> const& stateLock)
{
auto next = std::ranges::min_element(
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 const next = std::ranges::min_element(
sites_, [](Site const& a, Site const& b) { return a.nextRefresh < b.nextRefresh; });
if (next != sites_.end())
@@ -333,7 +347,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";
@@ -341,6 +355,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::scoped_lock const lockState{state_mutex_};