mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-01 19:42:12 +00:00
Compare commits
36 Commits
vvysokikh/
...
ximinez/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2faef541 | ||
|
|
5d2be06748 | ||
|
|
2f5b17c2cb | ||
|
|
a963e4a389 | ||
|
|
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 |
@@ -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);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(ConfidentialTransfer, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -889,7 +889,6 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
|
||||
MustDeleteAcct | DestroyMptIssuance | MustModifyVault,
|
||||
({
|
||||
{sfVaultID, SoeRequired},
|
||||
{sfMemoData, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction trades assets for shares with a vault. */
|
||||
|
||||
@@ -57,32 +57,6 @@ public:
|
||||
{
|
||||
return this->tx_->at(sfVaultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMemoData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getMemoData() const
|
||||
{
|
||||
if (hasMemoData())
|
||||
{
|
||||
return this->tx_->at(sfMemoData);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMemoData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMemoData() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMemoData);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -138,17 +112,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMemoData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
VaultDeleteBuilder&
|
||||
setMemoData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMemoData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the VaultDelete wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -618,6 +618,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, SLE::ref offer)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#include <xrpl/tx/transactors/check/CheckCancel.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -21,9 +19,6 @@ namespace xrpl {
|
||||
NotTEC
|
||||
CheckCancel::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.rules.enabled(fixCleanup3_3_0) && ctx.tx[sfCheckID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
@@ -50,9 +49,6 @@ CheckCash::checkExtraFeatures(xrpl::PreflightContext const& ctx)
|
||||
NotTEC
|
||||
CheckCash::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.rules.enabled(fixCleanup3_3_0) && ctx.tx[sfCheckID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
// Exactly one of Amount or DeliverMin must be present.
|
||||
auto const optAmount = ctx.tx[~sfAmount];
|
||||
auto const optDeliverMin = ctx.tx[~sfDeliverMin];
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||
@@ -30,12 +28,6 @@ VaultDelete::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfMemoData) && !ctx.rules.enabled(featureLendingProtocolV1_1))
|
||||
return temDISABLED;
|
||||
|
||||
if (!validDataLength(ctx.tx[~sfMemoData], kMaxDataPayloadLength))
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1257,12 +1257,6 @@ class Check_test : public beast::unit_test::Suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Zero CheckID is malformed once fixCleanup3_3_0 is active; before
|
||||
// that it simply misses the ledger lookup.
|
||||
env(check::cash(bob, uint256{}, usd(20)),
|
||||
Ter(features[fixCleanup3_3_0] ? TER{temMALFORMED} : TER{tecNO_ENTRY}));
|
||||
env.close();
|
||||
|
||||
// alice creates her checks ahead of time.
|
||||
uint256 const chkIdU{getCheckIndex(alice, env.seq(alice))};
|
||||
env(check::create(alice, bob, usd(20)));
|
||||
@@ -1710,12 +1704,6 @@ class Check_test : public beast::unit_test::Suite
|
||||
// Non-existent check.
|
||||
env(check::cancel(bob, getCheckIndex(alice, env.seq(alice))), Ter(tecNO_ENTRY));
|
||||
env.close();
|
||||
|
||||
// Zero CheckID is malformed once fixCleanup3_3_0 is active; before
|
||||
// that it simply misses the ledger lookup.
|
||||
env(check::cancel(bob, uint256{}),
|
||||
Ter(features[fixCleanup3_3_0] ? TER{temMALFORMED} : TER{tecNO_ENTRY}));
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2510,8 +2498,6 @@ public:
|
||||
using namespace test::jtx;
|
||||
auto const sa = testableAmendments();
|
||||
testWithFeats(sa);
|
||||
testCancelInvalid(sa - fixCleanup3_3_0);
|
||||
testCashInvalid(sa - fixCleanup3_3_0);
|
||||
testTrustLineCreation(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7511,74 +7511,6 @@ class Vault_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testVaultDeleteMemoData()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(1'000'000), owner);
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
|
||||
auto const keylet = keylet::vault(owner.id(), 1);
|
||||
auto delTx = vault.del({.owner = owner, .id = keylet.key});
|
||||
|
||||
// Test VaultDelete with featureLendingProtocolV1_1 disabled
|
||||
// Transaction fails if the data field is provided
|
||||
{
|
||||
testcase("VaultDelete memo data featureLendingProtocolV1_1 disabled");
|
||||
env.disableFeature(featureLendingProtocolV1_1);
|
||||
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength, 'A'));
|
||||
env(delTx, Ter(temDISABLED));
|
||||
env.enableFeature(featureLendingProtocolV1_1);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Transaction fails if the data field is too large
|
||||
{
|
||||
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled data too large");
|
||||
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength + 1, 'A'));
|
||||
env(delTx, Ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Transaction fails if the data field is set, but is empty
|
||||
{
|
||||
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled data empty");
|
||||
delTx[sfMemoData] = strHex(std::string(0, 'A'));
|
||||
env(delTx, Ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled no vault");
|
||||
auto const keylet = keylet::vault(owner.id(), env.seq(owner));
|
||||
|
||||
// Recreate the transaction as the vault keylet changed
|
||||
auto delTx = vault.del({.owner = owner, .id = keylet.key});
|
||||
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength, 'A'));
|
||||
env(delTx, Ter(tecNO_ENTRY));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled data valid");
|
||||
PrettyAsset const xrpAsset = xrpIssue();
|
||||
auto const [tx, keylet] = vault.create({.owner = owner, .asset = xrpAsset});
|
||||
env(tx, Ter(tesSUCCESS));
|
||||
env.close();
|
||||
// Recreate the transaction as the vault keylet changed
|
||||
auto delTx = vault.del({.owner = owner, .id = keylet.key});
|
||||
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength, 'A'));
|
||||
env(delTx, Ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testVaultDepositFreeze()
|
||||
{
|
||||
@@ -8150,7 +8082,6 @@ class Vault_test : public beast::unit_test::Suite
|
||||
|
||||
runTests();
|
||||
env.disableFeature(fixCleanup3_3_0);
|
||||
|
||||
runTests();
|
||||
env.enableFeature(fixCleanup3_3_0);
|
||||
}
|
||||
@@ -8184,7 +8115,6 @@ public:
|
||||
testVaultClawbackAssets();
|
||||
testVaultEscrowedMPT();
|
||||
testAssetsMaximum();
|
||||
testVaultDeleteMemoData();
|
||||
testBug6LimitBypassWithShares();
|
||||
testRemoveEmptyHoldingLockedAmount();
|
||||
testRemoveEmptyHoldingConfidentialBalances();
|
||||
|
||||
@@ -30,7 +30,6 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const memoDataValue = canonical_VL();
|
||||
|
||||
VaultDeleteBuilder builder{
|
||||
accountValue,
|
||||
@@ -40,7 +39,6 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setMemoData(memoDataValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -64,14 +62,6 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = memoDataValue;
|
||||
auto const actualOpt = tx.getMemoData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMemoData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfMemoData");
|
||||
EXPECT_TRUE(tx.hasMemoData());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
@@ -89,7 +79,6 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const memoDataValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
VaultDeleteBuilder initialBuilder{
|
||||
@@ -99,7 +88,6 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setMemoData(memoDataValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -124,13 +112,6 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = memoDataValue;
|
||||
auto const actualOpt = rebuiltTx.getMemoData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMemoData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfMemoData");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
@@ -161,35 +142,5 @@ TEST(TransactionsVaultDeleteTests, BuilderThrowsOnWrongTxType)
|
||||
EXPECT_THROW(VaultDeleteBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsVaultDeleteTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testVaultDeleteNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
|
||||
VaultDeleteBuilder builder{
|
||||
accountValue,
|
||||
vaultIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasMemoData());
|
||||
EXPECT_FALSE(tx.getMemoData().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -343,6 +357,9 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec)
|
||||
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{stateMutex_};
|
||||
|
||||
Reference in New Issue
Block a user