mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Compare commits
15 Commits
bthomee/ap
...
ximinez/em
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4a37fb3a4 | ||
|
|
5faacf6006 | ||
|
|
cabee3faac | ||
|
|
7ed2258782 | ||
|
|
f97b4d01fb | ||
|
|
e657df5fe1 | ||
|
|
ca190b5aaa | ||
|
|
503014f03f | ||
|
|
29f5829680 | ||
|
|
2b1f7f9d55 | ||
|
|
c776515cee | ||
|
|
5d9b00dba4 | ||
|
|
a81d37465e | ||
|
|
e341af4aee | ||
|
|
8122ed62b6 |
@@ -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);
|
||||
|
||||
@@ -19,6 +19,7 @@ XRPL_FIX (Cleanup3_2_0, Supported::no, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(MPTokensV2, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -372,6 +372,22 @@ public:
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: An account's directory should never be empty
|
||||
*
|
||||
*/
|
||||
class NoEmptyDirectory
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
// additional invariant checks can be declared above and then added to this
|
||||
// tuple
|
||||
using InvariantChecks = std::tuple<
|
||||
@@ -399,7 +415,8 @@ using InvariantChecks = std::tuple<
|
||||
ValidLoanBroker,
|
||||
ValidLoan,
|
||||
ValidVault,
|
||||
ValidMPTPayment>;
|
||||
ValidMPTPayment,
|
||||
NoEmptyDirectory>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -81,9 +81,12 @@ setCurrentThreadNameImpl(std::string_view name)
|
||||
{
|
||||
// truncate and set the thread name.
|
||||
char boundedName[maxThreadNameLength + 1];
|
||||
auto const boundedSize = name.size() < maxThreadNameLength ? name.size() : maxThreadNameLength;
|
||||
name.copy(boundedName, boundedSize);
|
||||
boundedName[boundedSize] = '\0';
|
||||
std::snprintf(
|
||||
boundedName,
|
||||
sizeof(boundedName),
|
||||
"%.*s",
|
||||
static_cast<int>(maxThreadNameLength),
|
||||
name.data()); // NOLINT(bugprone-suspicious-stringview-data-usage)
|
||||
|
||||
pthread_setname_np(pthread_self(), boundedName);
|
||||
|
||||
|
||||
@@ -253,6 +253,7 @@ ApplyView::emptyDirDelete(Keylet const& directory)
|
||||
bool
|
||||
ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot)
|
||||
{
|
||||
keepRoot = false;
|
||||
auto node = peek(keylet::page(directory, page));
|
||||
|
||||
if (!node)
|
||||
|
||||
@@ -621,6 +621,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)
|
||||
{
|
||||
|
||||
@@ -1043,4 +1043,42 @@ NoModifiedUnmodifiableFields::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
NoEmptyDirectory::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (isDelete)
|
||||
return;
|
||||
if (before && before->getType() != ltDIR_NODE)
|
||||
return;
|
||||
if (after && after->getType() != ltDIR_NODE)
|
||||
return;
|
||||
if (!after->isFieldPresent(sfOwner))
|
||||
// Not an account dir
|
||||
return;
|
||||
|
||||
bad_ = after->at(sfIndexes).empty();
|
||||
}
|
||||
|
||||
bool
|
||||
NoEmptyDirectory::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (bad_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: empty owner directory.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -68,12 +68,15 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (hasExpired(ctx.view, (*offerSLE)[~sfExpiration]))
|
||||
{
|
||||
// Before fixSecurity3_1_3 amendment, expired offers caused tecEXPIRED in preclaim,
|
||||
// leaving them on ledger forever. After the amendment, we allow expired offers to
|
||||
// reach doApply() where they get deleted and tecEXPIRED is returned.
|
||||
if (!ctx.view.rules().enabled(fixSecurity3_1_3))
|
||||
// Before fixExpiredNFTokenOfferRemoval amendment, expired
|
||||
// offers caused tecEXPIRED in preclaim, leaving them on ledger
|
||||
// forever. After the amendment, we allow expired offers to
|
||||
// reach doApply() where they get deleted and tecEXPIRED is
|
||||
// returned.
|
||||
if (!ctx.view.rules().enabled(fixExpiredNFTokenOfferRemoval))
|
||||
return {nullptr, tecEXPIRED};
|
||||
// Amendment enabled: return the expired offer to be handled in doApply.
|
||||
// Amendment enabled: return the expired offer to be handled in
|
||||
// doApply
|
||||
}
|
||||
|
||||
if ((*offerSLE)[sfAmount].negative())
|
||||
@@ -447,9 +450,10 @@ NFTokenAcceptOffer::doApply()
|
||||
auto bo = loadToken(ctx_.tx[~sfNFTokenBuyOffer]);
|
||||
auto so = loadToken(ctx_.tx[~sfNFTokenSellOffer]);
|
||||
|
||||
// With fixSecurity3_1_3 amendment, check for expired offers and delete them, returning
|
||||
// tecEXPIRED. This ensures expired offers are properly cleaned up from the ledger.
|
||||
if (view().rules().enabled(fixSecurity3_1_3))
|
||||
// With fixExpiredNFTokenOfferRemoval amendment, check for expired offers
|
||||
// and delete them, returning tecEXPIRED. This ensures expired offers
|
||||
// are properly cleaned up from the ledger.
|
||||
if (view().rules().enabled(fixExpiredNFTokenOfferRemoval))
|
||||
{
|
||||
bool foundExpired = false;
|
||||
|
||||
|
||||
@@ -1096,10 +1096,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
|
||||
// The buy offer must not have expired.
|
||||
// NOTE: this is only a preclaim check with the
|
||||
// fixSecurity3_1_3 amendment disabled.
|
||||
// fixExpiredNFTokenOfferRemoval amendment disabled.
|
||||
env(token::acceptBuyOffer(alice, buyerExpOfferIndex), ter(tecEXPIRED));
|
||||
env.close();
|
||||
if (features[fixSecurity3_1_3])
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
buyerCount--;
|
||||
}
|
||||
@@ -1117,12 +1117,12 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
|
||||
// The sell offer must not have expired.
|
||||
// NOTE: this is only a preclaim check with the
|
||||
// fixSecurity3_1_3 amendment disabled.
|
||||
// fixExpiredNFTokenOfferRemoval amendment disabled.
|
||||
env(token::acceptSellOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
|
||||
env.close();
|
||||
// Alice's count is decremented by one when the expired offer is
|
||||
// removed.
|
||||
if (features[fixSecurity3_1_3])
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
aliceCount--;
|
||||
}
|
||||
@@ -3101,10 +3101,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
// No one can accept an expired sell offer.
|
||||
env(token::acceptSellOffer(buyer, offer1), ter(tecEXPIRED));
|
||||
|
||||
// With fixSecurity3_1_3 amendment, the first accept
|
||||
// With fixExpiredNFTokenOfferRemoval amendment, the first accept
|
||||
// attempt deletes the expired offer. Without the amendment,
|
||||
// the offer remains and we can try to accept it again.
|
||||
if (features[fixSecurity3_1_3])
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// After amendment: offer was deleted by first accept attempt
|
||||
minterCount--;
|
||||
@@ -3123,7 +3123,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
if (!features[fixSecurity3_1_3])
|
||||
if (!features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// Before amendment: expired offer still exists and needs to be
|
||||
// cancelled
|
||||
@@ -3189,10 +3189,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
// An expired buy offer cannot be accepted.
|
||||
env(token::acceptBuyOffer(minter, offer1), ter(tecEXPIRED));
|
||||
|
||||
// With fixSecurity3_1_3 amendment, the first accept
|
||||
// With fixExpiredNFTokenOfferRemoval amendment, the first accept
|
||||
// attempt deletes the expired offer. Without the amendment,
|
||||
// the offer remains and we can try to accept it again.
|
||||
if (features[fixSecurity3_1_3])
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// After amendment: offer was deleted by first accept attempt
|
||||
buyerCount--;
|
||||
@@ -3211,7 +3211,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
if (!features[fixSecurity3_1_3])
|
||||
if (!features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// Before amendment: expired offer still exists and can be
|
||||
// cancelled
|
||||
@@ -3288,7 +3288,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env(token::brokerOffers(issuer, buyOffer1, sellOffer1), ter(tecEXPIRED));
|
||||
env.close();
|
||||
|
||||
if (features[fixSecurity3_1_3])
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// With amendment: expired offers are deleted
|
||||
minterCount--;
|
||||
@@ -3298,7 +3298,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
if (features[fixSecurity3_1_3])
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// The buy offer was deleted, so no need to cancel it
|
||||
// The sell offer still exists, so we can cancel it
|
||||
@@ -3377,7 +3377,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
if (features[fixSecurity3_1_3])
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// After amendment: expired offers were deleted during broker
|
||||
// attempt
|
||||
@@ -3463,7 +3463,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
|
||||
// The expired offers are still in the ledger.
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
if (!features[fixSecurity3_1_3])
|
||||
if (!features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// Before amendment: expired offers still exist in ledger
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
@@ -7190,7 +7190,7 @@ public:
|
||||
{
|
||||
testWithFeats(
|
||||
allFeatures - fixNFTokenReserve - featureNFTokenMintOffer - featureDynamicNFT -
|
||||
fixSecurity3_1_3);
|
||||
fixExpiredNFTokenOfferRemoval);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7227,7 +7227,7 @@ class NFTokenWOExpiredOfferRemoval_test : public NFTokenBaseUtil_test
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testWithFeats(allFeatures - fixSecurity3_1_3);
|
||||
testWithFeats(allFeatures - fixExpiredNFTokenOfferRemoval);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1201,7 +1201,7 @@ class LedgerEntry_test : public beast::unit_test::suite
|
||||
checkErrorValue(
|
||||
jrr[jss::result],
|
||||
"malformedAuthorizedCredentials",
|
||||
"Invalid field 'authorized_credentials', not array of objects.");
|
||||
"Invalid field 'authorized_credentials', not array.");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1219,7 +1219,7 @@ class LedgerEntry_test : public beast::unit_test::suite
|
||||
checkErrorValue(
|
||||
jrr[jss::result],
|
||||
"malformedAuthorizedCredentials",
|
||||
"Invalid field 'authorized_credentials', not array of objects.");
|
||||
"Invalid field 'authorized_credentials', not array.");
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -2639,7 +2639,7 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
|
||||
{
|
||||
fee_.update(
|
||||
Resource::feeModerateBurdenPeer,
|
||||
"Reply limit reached. Truncating reply.");
|
||||
" Reply limit reached. Truncating reply.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ parseAuthorizeCredentials(Json::Value const& jv)
|
||||
if (!jo.isObject())
|
||||
{
|
||||
return LedgerEntryHelpers::invalidFieldError(
|
||||
"malformedAuthorizedCredentials", jss::authorized_credentials, "array of objects");
|
||||
"malformedAuthorizedCredentials", jss::authorized_credentials, "array");
|
||||
}
|
||||
|
||||
if (auto const value = LedgerEntryHelpers::hasRequired(
|
||||
|
||||
Reference in New Issue
Block a user