mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 09:16:47 +00:00
Compare commits
39 Commits
mvadari/se
...
ximinez/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2be06748 | ||
|
|
93ac1aa7aa | ||
|
|
d9a3af8207 | ||
|
|
2f5b17c2cb | ||
|
|
8d1083e5ea | ||
|
|
a963e4a389 | ||
|
|
1e45d363c5 | ||
|
|
962f3ee744 | ||
|
|
2a7499a415 | ||
|
|
ae8c44b606 | ||
|
|
1edd6fe4bf | ||
|
|
2edf310a6f | ||
|
|
7af68be818 | ||
|
|
19fc5c48dd | ||
|
|
2f43a1901d | ||
|
|
6a0440bf6d | ||
|
|
cf398d05aa | ||
|
|
59a8e30a3e | ||
|
|
8160921783 | ||
|
|
6dc01196b4 | ||
|
|
cdbbbdd27f | ||
|
|
3d30c09031 | ||
|
|
e59ca23487 | ||
|
|
72c700c3e0 | ||
|
|
4589fcbcfc | ||
|
|
86d840f53d | ||
|
|
741b61cdf3 | ||
|
|
6bb0989c9f | ||
|
|
9120506613 | ||
|
|
3b3de96bd4 | ||
|
|
c9ab6ab25f | ||
|
|
fb0605cfd3 | ||
|
|
156553bb5e | ||
|
|
781b56849b | ||
|
|
278c02bebb | ||
|
|
1d6fedf9a2 | ||
|
|
2e8de499aa | ||
|
|
0bce3639a6 | ||
|
|
8f329e3bc6 |
4
.github/workflows/reusable-package.yml
vendored
4
.github/workflows/reusable-package.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -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 = ::
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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_};
|
||||
|
||||
Reference in New Issue
Block a user