diff --git a/src/ripple/app/hook/Enum.h b/src/ripple/app/hook/Enum.h index a3ba418dd..2acf37183 100644 --- a/src/ripple/app/hook/Enum.h +++ b/src/ripple/app/hook/Enum.h @@ -66,6 +66,13 @@ maxNamespaces(void) return 256; } +// maximum number of entires in a namespace to delete with ns delete +inline uint32_t +maxNamespaceDelete(void) +{ + return 256; +} + enum TSHFlags : uint8_t { tshNONE = 0b000, tshROLLBACK = 0b001, diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 99a330764..0b18e6a8d 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -1798,7 +1798,7 @@ hook::finalizeHookState( TER result = setHookState(applyCtx, acc, ns, key, slice); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) { JLOG(j.warn()) << "HookError[TX:" << txnID @@ -3527,7 +3527,7 @@ DEFINE_HOOK_FUNCTION( ripple::ApplyFlags::tapPREFLIGHT_EMIT, j); - if (preflightResult.ter != tesSUCCESS) + if (!isTesSuccess(preflightResult.ter)) { JLOG(j.trace()) << "HookEmit[" << HC_ACC() << "]: Transaction preflight failure: " diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 62da31163..7a3175076 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -1431,7 +1431,7 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) app_.getHashRouter().setFlags(e.transaction->getID(), SF_BAD); #ifdef DEBUG - if (e.result != tesSUCCESS) + if (!isTesSuccess(e.result)) { std::string token, human; @@ -1445,7 +1445,7 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) bool addLocal = e.local; - if (e.result == tesSUCCESS) + if (isTesSuccess(e.result)) { JLOG(m_journal.debug()) << "Transaction is now included in open ledger"; @@ -3215,7 +3215,7 @@ NetworkOPsImp::pubValidatedTransaction( } } - if (transaction.getResult() == tesSUCCESS) + if (isTesSuccess(transaction.getResult())) app_.getOrderBookDB().processTxn(ledger, transaction, jvObj); pubAccountTransaction(ledger, transaction); diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index 379eb1ee1..72898ee7e 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -755,7 +755,7 @@ TxQ::apply( // etc. before doing potentially expensive queue // replace and multi-transaction operations. auto const pfresult = preflight(app, view.rules(), *tx, flags, j); - if (pfresult.ter != tesSUCCESS) + if (!isTesSuccess(pfresult.ter)) return {pfresult.ter, false}; // If the account is not currently in the ledger, don't queue its tx. diff --git a/src/ripple/app/paths/Flow.cpp b/src/ripple/app/paths/Flow.cpp index f177cfc11..374eed284 100644 --- a/src/ripple/app/paths/Flow.cpp +++ b/src/ripple/app/paths/Flow.cpp @@ -42,7 +42,7 @@ finishFlow( FlowResult&& f) { path::RippleCalc::Output result; - if (f.ter == tesSUCCESS) + if (isTesSuccess(f.ter)) f.sandbox->apply(sb); else result.removableOffers = std::move(f.removableOffers); @@ -100,7 +100,7 @@ flow( offerCrossing, j); - if (toStrandsTer != tesSUCCESS) + if (!isTesSuccess(toStrandsTer)) { path::RippleCalc::Output result; result.setResult(toStrandsTer); diff --git a/src/ripple/app/paths/PathRequest.cpp b/src/ripple/app/paths/PathRequest.cpp index 8fd42d6a3..5d950e3b1 100644 --- a/src/ripple/app/paths/PathRequest.cpp +++ b/src/ripple/app/paths/PathRequest.cpp @@ -603,7 +603,7 @@ PathRequest::findPaths( ps, // --> Path set. app_.logs()); - if (rc.result() != tesSUCCESS) + if (!isTesSuccess(rc.result())) { JLOG(m_journal.warn()) << iIdentifier << " Failed with covering path " @@ -617,7 +617,7 @@ PathRequest::findPaths( } } - if (rc.result() == tesSUCCESS) + if (isTesSuccess(rc.result())) { Json::Value jvEntry(Json::objectValue); rc.actualAmountIn.setIssuer(sourceAccount); diff --git a/src/ripple/app/paths/Pathfinder.cpp b/src/ripple/app/paths/Pathfinder.cpp index 556622ee7..c529aac7c 100644 --- a/src/ripple/app/paths/Pathfinder.cpp +++ b/src/ripple/app/paths/Pathfinder.cpp @@ -372,7 +372,7 @@ Pathfinder::getPathLiquidity( app_.logs(), &rcInput); // If we can't get even the minimum liquidity requested, we're done. - if (rc.result() != tesSUCCESS) + if (!isTesSuccess(rc.result())) return rc.result(); qualityOut = getRate(rc.actualAmountOut, rc.actualAmountIn); @@ -393,7 +393,7 @@ Pathfinder::getPathLiquidity( &rcInput); // If we found further liquidity, add it into the result. - if (rc.result() == tesSUCCESS) + if (isTesSuccess(rc.result())) amountOut += rc.actualAmountOut; } @@ -431,7 +431,7 @@ Pathfinder::computePathRanks( app_.logs(), &rcInput); - if (rc.result() == tesSUCCESS) + if (isTesSuccess(rc.result())) { JLOG(j_.debug()) << "Default path contributes: " << rc.actualAmountIn; @@ -520,7 +520,7 @@ Pathfinder::rankPaths( uint64_t uQuality; auto const resultCode = getPathLiquidity( currentPath, saMinDstAmount, liquidity, uQuality); - if (resultCode != tesSUCCESS) + if (!isTesSuccess(resultCode)) { JLOG(j_.debug()) << "findPaths: dropping : " << transToken(resultCode) diff --git a/src/ripple/app/paths/impl/BookStep.cpp b/src/ripple/app/paths/impl/BookStep.cpp index a6b2c5961..0c63b8c08 100644 --- a/src/ripple/app/paths/impl/BookStep.cpp +++ b/src/ripple/app/paths/impl/BookStep.cpp @@ -685,7 +685,7 @@ BookStep::consumeOffer( offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_); - if (dr != tesSUCCESS) + if (!isTesSuccess(dr)) Throw(dr); } @@ -698,7 +698,7 @@ BookStep::consumeOffer( book_.out.account, toSTAmount(ownerGives, book_.out), j_); - if (cr != tesSUCCESS) + if (!isTesSuccess(cr)) Throw(cr); } @@ -1170,7 +1170,7 @@ make_BookStepHelper(StrandContext const& ctx, Issue const& in, Issue const& out) ter = paymentStep->check(ctx); r = std::move(paymentStep); } - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return {ter, nullptr}; return {tesSUCCESS, std::move(r)}; diff --git a/src/ripple/app/paths/impl/DirectStep.cpp b/src/ripple/app/paths/impl/DirectStep.cpp index 00647121b..d4a618a04 100644 --- a/src/ripple/app/paths/impl/DirectStep.cpp +++ b/src/ripple/app/paths/impl/DirectStep.cpp @@ -900,7 +900,7 @@ DirectStepI::check(StrandContext const& ctx) const if (!(ctx.isLast && ctx.isFirst)) { auto const ter = checkFreeze(ctx.view, src_, dst_, currency_); - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return ter; } @@ -912,7 +912,7 @@ DirectStepI::check(StrandContext const& ctx) const { auto const ter = checkNoRipple(ctx.view, *prevSrc, src_, dst_, currency_, j_); - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return ter; } } @@ -995,7 +995,7 @@ make_DirectStepI( ter = paymentStep->check(ctx); r = std::move(paymentStep); } - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return {ter, nullptr}; return {tesSUCCESS, std::move(r)}; diff --git a/src/ripple/app/paths/impl/PaySteps.cpp b/src/ripple/app/paths/impl/PaySteps.cpp index 578d73fb7..8e2db3345 100644 --- a/src/ripple/app/paths/impl/PaySteps.cpp +++ b/src/ripple/app/paths/impl/PaySteps.cpp @@ -318,7 +318,7 @@ toStrand( cur->getAccountID(), curIssue.account, curIssue.currency); - if (msr.first != tesSUCCESS) + if (!isTesSuccess(msr.first)) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); impliedPE.emplace( @@ -339,7 +339,7 @@ toStrand( cur->getAccountID(), curIssue.account, curIssue.currency); - if (msr.first != tesSUCCESS) + if (!isTesSuccess(msr.first)) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); impliedPE.emplace( @@ -364,7 +364,7 @@ toStrand( // Last step. insert xrp endpoint step auto msr = make_XRPEndpointStep(ctx(), next->getAccountID()); - if (msr.first != tesSUCCESS) + if (!isTesSuccess(msr.first)) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); } @@ -377,7 +377,7 @@ toStrand( curIssue.account, next->getAccountID(), curIssue.currency); - if (msr.first != tesSUCCESS) + if (!isTesSuccess(msr.first)) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); } @@ -395,7 +395,7 @@ toStrand( auto s = toStep( ctx(/*isLast*/ i == normPath.size() - 2), cur, next, curIssue); - if (s.first == tesSUCCESS) + if (isTesSuccess(s.first)) result.emplace_back(std::move(s.second)); else { @@ -503,7 +503,7 @@ toStrands( auto const ter = sp.first; auto& strand = sp.second; - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) { JLOG(j.trace()) << "failed to add default path"; if (isTemMalformed(ter) || paths.empty()) @@ -546,7 +546,7 @@ toStrands( auto ter = sp.first; auto& strand = sp.second; - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) { lastFailTer = ter; JLOG(j.trace()) << "failed to add path: ter: " << ter diff --git a/src/ripple/app/paths/impl/XRPEndpointStep.cpp b/src/ripple/app/paths/impl/XRPEndpointStep.cpp index 487846380..f765d61f6 100644 --- a/src/ripple/app/paths/impl/XRPEndpointStep.cpp +++ b/src/ripple/app/paths/impl/XRPEndpointStep.cpp @@ -267,7 +267,7 @@ XRPEndpointStep::revImp( auto& sender = isLast_ ? xrpAccount() : acc_; auto& receiver = isLast_ ? acc_ : xrpAccount(); auto ter = accountSend(sb, sender, receiver, toSTAmount(result), j_); - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return {XRPAmount{beast::zero}, XRPAmount{beast::zero}}; cache_.emplace(result); @@ -290,7 +290,7 @@ XRPEndpointStep::fwdImp( auto& sender = isLast_ ? xrpAccount() : acc_; auto& receiver = isLast_ ? acc_ : xrpAccount(); auto ter = accountSend(sb, sender, receiver, toSTAmount(result), j_); - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return {XRPAmount{beast::zero}, XRPAmount{beast::zero}}; cache_.emplace(result); @@ -359,7 +359,7 @@ XRPEndpointStep::check(StrandContext const& ctx) const auto& src = isLast_ ? xrpAccount() : acc_; auto& dst = isLast_ ? acc_ : xrpAccount(); auto ter = checkFreeze(ctx.view, src, dst, xrpCurrency()); - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return ter; if (ctx.view.rules().enabled(fix1781)) @@ -413,7 +413,7 @@ make_XRPEndpointStep(StrandContext const& ctx, AccountID const& acc) ter = paymentStep->check(ctx); r = std::move(paymentStep); } - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) return {ter, nullptr}; return {tesSUCCESS, std::move(r)}; diff --git a/src/ripple/app/tx/applySteps.h b/src/ripple/app/tx/applySteps.h index d71c05c74..336d81e0c 100644 --- a/src/ripple/app/tx/applySteps.h +++ b/src/ripple/app/tx/applySteps.h @@ -216,7 +216,7 @@ public: , flags(ctx_.flags) , j(ctx_.j) , ter(ter_) - , likelyToClaimFee(ter == tesSUCCESS || isTecClaimHardFail(ter, flags)) + , likelyToClaimFee(isTesSuccess(ter) || isTecClaimHardFail(ter, flags)) { } diff --git a/src/ripple/app/tx/impl/CashCheck.cpp b/src/ripple/app/tx/impl/CashCheck.cpp index 187cf6eda..2ffb13ba6 100644 --- a/src/ripple/app/tx/impl/CashCheck.cpp +++ b/src/ripple/app/tx/impl/CashCheck.cpp @@ -326,7 +326,7 @@ CashCheck::doApply() // The source account has enough XRP so make the ledger change. if (TER const ter{ transferXRP(psb, srcId, account_, xrpDeliver, viewJ)}; - ter != tesSUCCESS) + !isTesSuccess(ter)) { // The transfer failed. Return the error code. return ter; @@ -452,7 +452,7 @@ CashCheck::doApply() sleCheck->getFieldAmount(sfSendMax), viewJ); - if (result.result() != tesSUCCESS) + if (!isTesSuccess(result.result())) { JLOG(ctx_.journal.warn()) << "flow failed when cashing check."; return result.result(); diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp index 0837d1096..05136fcfa 100644 --- a/src/ripple/app/tx/impl/CreateOffer.cpp +++ b/src/ripple/app/tx/impl/CreateOffer.cpp @@ -212,7 +212,7 @@ CreateOffer::preclaim(PreclaimContext const& ctx) id, ctx.j, Issue(uPaysCurrency, uPaysIssuerID)); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; } @@ -495,7 +495,7 @@ CreateOffer::bridged_cross( } } - if (cross_result != tesSUCCESS) + if (!isTesSuccess(cross_result)) { cross_result = tecFAILED_PROCESSING; break; @@ -585,7 +585,7 @@ CreateOffer::direct_cross( have_offer = step_account(offers, taker); } - if (cross_result != tesSUCCESS) + if (!isTesSuccess(cross_result)) { cross_result = tecFAILED_PROCESSING; break; @@ -989,7 +989,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) bool const bOpenLedger = sb.open(); bool crossed = false; - if (result == tesSUCCESS) + if (isTesSuccess(result)) { // If a tick size applies, round the offer to the tick size auto const& uPaysIssuerID = saTakerPays.getIssuer(); @@ -1059,7 +1059,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) // We expect the implementation of cross to succeed // or give a tec. - assert(result == tesSUCCESS || isTecClaim(result)); + assert(isTesSuccess(result) || isTecClaim(result)); if (auto stream = j_.trace()) { @@ -1071,7 +1071,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) if (result == tecFAILED_PROCESSING && bOpenLedger) result = telFAILED_PROCESSING; - if (result != tesSUCCESS) + if (!isTesSuccess(result)) { JLOG(j_.debug()) << "final result: " << transToken(result); return {result, true}; @@ -1108,7 +1108,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) assert(saTakerPays > zero && saTakerGets > zero); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) { JLOG(j_.debug()) << "final result: " << transToken(result); return {result, true}; @@ -1161,7 +1161,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) if (!crossed) result = tecINSUF_RESERVE_OFFER; - if (result != tesSUCCESS) + if (!isTesSuccess(result)) { JLOG(j_.debug()) << "final result: " << transToken(result); } diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index 1b5d559b1..74ee09a1a 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -802,7 +802,7 @@ EscrowCancel::doApply() // dry run before we make any changes to ledger if (TER const result = trustAdjustLockedBalance( ctx_.view(), sleLine, -amount, -1, ctx_.journal, DryRun); - result != tesSUCCESS) + !isTesSuccess(result)) return result; } } diff --git a/src/ripple/app/tx/impl/Import.cpp b/src/ripple/app/tx/impl/Import.cpp index 14af32963..28289870c 100644 --- a/src/ripple/app/tx/impl/Import.cpp +++ b/src/ripple/app/tx/impl/Import.cpp @@ -49,7 +49,7 @@ Import::makeTxConsequences(PreflightContext const& ctx) return beast::zero; auto const result = meta->getFieldU8(sfTransactionResult); - if (result != tesSUCCESS && + if (!isTesSuccess(result) && !(result >= tecCLAIM && result <= tecLAST_POSSIBLE_ENTRY)) return beast::zero; @@ -220,7 +220,7 @@ Import::preflight(PreflightContext const& ctx) { uint8_t innerResult = meta->getFieldU8(sfTransactionResult); - if (innerResult == tesSUCCESS) + if (isTesSuccess(innerResult)) { // pass } @@ -972,7 +972,7 @@ Import::doSignerList(std::shared_ptr& sle, STTx const& stpTrans) TER result = SetSignerList::removeFromLedger(ctx_.app, sb, id, ctx_.journal); - if (result == tesSUCCESS) + if (isTesSuccess(result)) { JLOG(ctx_.journal.warn()) << "Import: successful destroy SignerListSet"; @@ -1057,7 +1057,7 @@ Import::doSignerList(std::shared_ptr& sle, STTx const& stpTrans) signers, sle->getFieldAmount(sfBalance).xrp()); - if (result == tesSUCCESS) + if (isTesSuccess(result)) { JLOG(ctx_.journal.warn()) << "Import: successful set SignerListSet"; sb.apply(ctx_.rawView()); @@ -1309,7 +1309,7 @@ Import::doApply() // Handle any key imports, but only if a tes code // these functions update the sle on their own // - if (meta->getFieldU8(sfTransactionResult) == tesSUCCESS) + if (isTesSuccess(meta->getFieldU8(sfTransactionResult))) { auto const tt = stpTrans->getTxnType(); if (tt == ttSIGNER_LIST_SET) diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 3cdf14bc6..e0843eb42 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -155,7 +155,7 @@ XRPNotCreated::finalize( return true; if (view.rules().enabled(featureImport) && tt == ttIMPORT && - res == tesSUCCESS) + isTesSuccess(res)) { // different rules for ttIMPORT auto const [inner, meta] = Import::getInnerTxn(tx, j); @@ -164,7 +164,7 @@ XRPNotCreated::finalize( auto const result = meta->getFieldU8(sfTransactionResult); - XRPAmount maxDropsAdded = result == tesSUCCESS || + XRPAmount maxDropsAdded = isTesSuccess(result) || (result >= tecCLAIM && result <= tecLAST_POSSIBLE_ENTRY) ? inner->getFieldAmount(sfFee).xrp() // burned in PoB : beast::zero; // if the txn didnt burn a fee we add nothing @@ -203,7 +203,7 @@ XRPNotCreated::finalize( } if (view.rules().enabled(featureXahauGenesis) && tt == ttGENESIS_MINT && - res == tesSUCCESS) + isTesSuccess(res)) { // different rules for ttGENESIS_MINT auto const& dests = tx.getFieldArray(sfGenesisMints); @@ -433,7 +433,7 @@ AccountRootsNotDeleted::finalize( ReadView const&, beast::Journal const& j) { - if (tx.getTxnType() == ttACCOUNT_DELETE && result == tesSUCCESS) + if (tx.getTxnType() == ttACCOUNT_DELETE && isTesSuccess(result)) { if (accountsDeleted_ == 1) return true; @@ -599,7 +599,7 @@ ValidNewAccountRoot::finalize( if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT || tt == ttREMIT) && - result == tesSUCCESS) + isTesSuccess(result)) { std::uint32_t const startingSeq{ view.rules().enabled(featureXahauGenesis) @@ -791,7 +791,7 @@ NFTokenCountTracking::finalize( if (tx.getTxnType() == ttNFTOKEN_MINT) { - if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal) + if (isTesSuccess(result) && beforeMintedTotal >= afterMintedTotal) { JLOG(j.fatal()) << "Invariant failed: successful minting didn't increase " @@ -799,7 +799,7 @@ NFTokenCountTracking::finalize( return false; } - if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal) + if (!isTesSuccess(result) && beforeMintedTotal != afterMintedTotal) { JLOG(j.fatal()) << "Invariant failed: failed minting changed the " "number of minted tokens."; @@ -817,7 +817,7 @@ NFTokenCountTracking::finalize( if (tx.getTxnType() == ttNFTOKEN_BURN) { - if (result == tesSUCCESS) + if (isTesSuccess(result)) { if (beforeBurnedTotal >= afterBurnedTotal) { @@ -828,7 +828,7 @@ NFTokenCountTracking::finalize( } } - if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal) + if (!isTesSuccess(result) && beforeBurnedTotal != afterBurnedTotal) { JLOG(j.fatal()) << "Invariant failed: failed burning changed the " "number of burned tokens."; diff --git a/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp b/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp index 61aa7e062..9e6376762 100644 --- a/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp +++ b/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp @@ -292,7 +292,7 @@ NFTokenAcceptOffer::pay( // several payouts in this tx, just confirm that the end state is OK. if (!view().rules().enabled(fixNonFungibleTokensV1_2)) return result; - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; if (accountFunds(view(), from, amount, fhZERO_IF_FROZEN, j_).signum() < 0) return tecINSUFFICIENT_FUNDS; diff --git a/src/ripple/app/tx/impl/NFTokenMint.cpp b/src/ripple/app/tx/impl/NFTokenMint.cpp index c26fb1fb1..901bf321b 100644 --- a/src/ripple/app/tx/impl/NFTokenMint.cpp +++ b/src/ripple/app/tx/impl/NFTokenMint.cpp @@ -257,7 +257,7 @@ NFTokenMint::doApply() if (TER const ret = nft::insertToken(ctx_.view(), account_, std::move(newToken)); - ret != tesSUCCESS) + !isTesSuccess(ret)) return ret; // Only check the reserve if the owner count actually changed. This diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp index c46bfe704..630db8823 100644 --- a/src/ripple/app/tx/impl/Payment.cpp +++ b/src/ripple/app/tx/impl/Payment.cpp @@ -430,7 +430,7 @@ Payment::doApply() // TODO: is this right? If the amount is the correct amount, was // the delivered amount previously set? - if (rc.result() == tesSUCCESS && rc.actualAmountOut != saDstAmount) + if (isTesSuccess(rc.result()) && rc.actualAmountOut != saDstAmount) { if (deliverMin && rc.actualAmountOut < *deliverMin) rc.setResult(tecPATH_PARTIAL); diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index e8c527df3..c97c5e08b 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -511,7 +511,7 @@ Remit::doApply() std::vector{srcAccID, dstAccID}, amount.issue(), j); - canXfer != tesSUCCESS) + !isTesSuccess(canXfer)) return canXfer; // compute the amount the source will need to send @@ -550,7 +550,7 @@ Remit::doApply() // action the transfer if (TER result = accountSend(sb, srcAccID, dstAccID, amount, j, true); - result != tesSUCCESS) + !isTesSuccess(result)) return result; } } diff --git a/src/ripple/app/tx/impl/SetHook.cpp b/src/ripple/app/tx/impl/SetHook.cpp index dec4570ec..aac02753c 100644 --- a/src/ripple/app/tx/impl/SetHook.cpp +++ b/src/ripple/app/tx/impl/SetHook.cpp @@ -794,21 +794,12 @@ SetHook::destroyNamespace( return tefBAD_LEDGER; } - bool sleAccChanged = false; - - if (!sleAccount->isFieldPresent(sfHookNamespaces)) - { - // NSDELETE is an opportunistic deleter, following "delete if exists" - // logic this way the flag can't block the SetHook transaction just - // because, for example, the namespace was deleted in the previous - // transaction but the hsFlags have not changed. Thus this is not an - // error. Allow fall through to below in case, for some reason, the - // namespace directory *does* exist but does not appear in the vector. - } - else - { - sleAccChanged = hook::removeHookNamespaceEntry(*sleAccount, ns); - } + // NSDELETE is an opportunistic deleter, following "delete if exists" + // logic this way the flag can't block the SetHook transaction just + // because, for example, the namespace was deleted in the previous + // transaction but the hsFlags have not changed. Thus this is not an + // error. Allow fall through to below in case, for some reason, the + // namespace directory *does* exist but does not appear in the vector. Keylet dirKeylet = keylet::hookStateDir(account, ns); @@ -818,21 +809,16 @@ SetHook::destroyNamespace( auto sleDir = view.peek(dirKeylet); - if (!sleDir) - { - // directory doesn't exist, this is a success condition - if (sleAccChanged) - view.update(sleAccount); - return tesSUCCESS; - } + bool const dirExists = !!sleDir; + bool const dirEmpty = dirExists && dirIsEmpty(view, dirKeylet); - if (dirIsEmpty(view, dirKeylet)) + if (!dirExists || dirEmpty) { - // directory exists but is empty, so remove the root page - if (sleAccChanged) + // directory doesn't exist or is empty, this is a success condition + if (hook::removeHookNamespaceEntry(*sleAccount, ns)) view.update(sleAccount); - - view.erase(sleDir); + if (dirExists) + view.erase(sleDir); return tesSUCCESS; } @@ -845,13 +831,20 @@ SetHook::destroyNamespace( return tefINTERNAL; } - uint32_t stateCount = sleAccount->getFieldU32(sfHookStateCount); - uint32_t oldStateCount = stateCount; + bool const fixEnabled = ctx.rules.enabled(fixNSDelete); + bool partialDelete = false; + uint32_t oldStateCount = sleAccount->getFieldU32(sfHookStateCount); std::vector toDelete; toDelete.reserve(sleDir->getFieldV256(sfIndexes).size()); do { + if (fixEnabled && toDelete.size() >= hook::maxNamespaceDelete()) + { + partialDelete = true; + break; + } + // Make sure any directory node types that we find are the kind // we can delete. Keylet const itemKeylet{ltCHILD, dirEntry}; @@ -910,9 +903,9 @@ SetHook::destroyNamespace( return tefBAD_LEDGER; } view.erase(sleItem); - stateCount--; } + uint32_t stateCount = oldStateCount - toDelete.size(); if (stateCount > oldStateCount) { JLOG(ctx.j.fatal()) << "HookSet(" << hook::log::NSDELETE_COUNT << ")[" @@ -927,9 +920,14 @@ SetHook::destroyNamespace( else sleAccount->setFieldU32(sfHookStateCount, stateCount); - view.update(sleAccount); + if (ctx.rules.enabled(fixNSDelete)) + adjustOwnerCount(view, sleAccount, -toDelete.size(), ctx.j); - return tesSUCCESS; + if (!partialDelete && sleAccount->isFieldPresent(sfHookNamespaces)) + hook::removeHookNamespaceEntry(*sleAccount, ns); + + view.update(sleAccount); + return partialDelete ? tesPARTIAL : tesSUCCESS; } // returns true if the reference counted ledger entry should be marked for @@ -1453,7 +1451,7 @@ SetHook::setHook() TER result = updateHookParameters( ctx, hookSetObj->get(), oldDefSLE, oldParams, newHook); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; } @@ -1696,7 +1694,7 @@ SetHook::setHook() STArray{sfHookParameters}, newHook); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; // if grants are provided set them @@ -1790,6 +1788,7 @@ SetHook::setHook() if (mSourceBalance < requiredDrops) return tecINSUFFICIENT_RESERVE; } + TER nsDeleteResult = tesSUCCESS; { // execution to here means we will enact changes to the ledger: @@ -1804,7 +1803,9 @@ SetHook::setHook() // clean up any Namespace directories marked for deletion and any zero // reference Hook Definitions for (auto const& ns : namespacesToDestroy) - destroyNamespace(ctx, view(), account_, ns); + if (nsDeleteResult = destroyNamespace(ctx, view(), account_, ns); + !isTesSuccess(nsDeleteResult)) + break; // do any pending removals for (auto const& p : keyletsToDestroy) @@ -1895,7 +1896,7 @@ SetHook::setHook() view().update(accountSLE); } - return tesSUCCESS; + return nsDeleteResult; } } // namespace ripple diff --git a/src/ripple/app/tx/impl/SetSignerList.cpp b/src/ripple/app/tx/impl/SetSignerList.cpp index 62bd6ac67..516bd63a1 100644 --- a/src/ripple/app/tx/impl/SetSignerList.cpp +++ b/src/ripple/app/tx/impl/SetSignerList.cpp @@ -83,7 +83,7 @@ SetSignerList::preflight(PreflightContext const& ctx) auto const result = determineOperation(ctx.tx, ctx.flags, ctx.j); - if (std::get<0>(result) != tesSUCCESS) + if (!isTesSuccess(std::get<0>(result))) return std::get<0>(result); if (std::get<3>(result) == unknown) @@ -104,7 +104,7 @@ SetSignerList::preflight(PreflightContext const& ctx) account, ctx.j, ctx.rules); - if (ter != tesSUCCESS) + if (!isTesSuccess(ter)) { return ter; } @@ -137,7 +137,7 @@ SetSignerList::preCompute() { // Get the quorum and operation info. auto result = determineOperation(ctx_.tx, view().flags(), j_); - assert(std::get<0>(result) == tesSUCCESS); + assert(isTesSuccess(std::get<0>(result))); assert(std::get<3>(result) != unknown); quorum_ = std::get<1>(result); diff --git a/src/ripple/app/tx/impl/SignerEntries.cpp b/src/ripple/app/tx/impl/SignerEntries.cpp index 15c4f599c..e646d2cf2 100644 --- a/src/ripple/app/tx/impl/SignerEntries.cpp +++ b/src/ripple/app/tx/impl/SignerEntries.cpp @@ -116,7 +116,7 @@ SignerEntries::determineOperation( // Rules const& rules) // { -// if (ter != tesSUCCESS) +// if (!isTesSuccess(ter)) // return ter; // if (op == unknown) @@ -137,7 +137,7 @@ SignerEntries::determineOperation( // account, // j, // rules); -// if (ter != tesSUCCESS) +// if (!isTesSuccess(ter)) // { // return ter; // } diff --git a/src/ripple/app/tx/impl/Taker.cpp b/src/ripple/app/tx/impl/Taker.cpp index f463ce411..623d8ed08 100644 --- a/src/ripple/app/tx/impl/Taker.cpp +++ b/src/ripple/app/tx/impl/Taker.cpp @@ -691,11 +691,11 @@ Taker::fill(BasicTaker::Flow const& flow, Offer& offer) { assert(!isXRP(flow.order.in)); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = redeemIOU(account(), flow.issuers.in, flow.issuers.in.issue()); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = issueIOU(offer.owner(), flow.order.in, flow.order.in.issue()); } @@ -703,7 +703,7 @@ Taker::fill(BasicTaker::Flow const& flow, Offer& offer) { assert(isXRP(flow.order.in)); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = transferXRP(account(), offer.owner(), flow.order.in); } @@ -712,11 +712,11 @@ Taker::fill(BasicTaker::Flow const& flow, Offer& offer) { assert(!isXRP(flow.order.out)); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = redeemIOU( offer.owner(), flow.issuers.out, flow.issuers.out.issue()); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = issueIOU(account(), flow.order.out, flow.order.out.issue()); } @@ -724,11 +724,11 @@ Taker::fill(BasicTaker::Flow const& flow, Offer& offer) { assert(isXRP(flow.order.out)); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = transferXRP(offer.owner(), account(), flow.order.out); } - if (result == tesSUCCESS) + if (isTesSuccess(result)) direct_crossings_++; return result; @@ -751,32 +751,32 @@ Taker::fill( // Taker to leg1: IOU if (leg1.owner() != account()) { - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = redeemIOU( account(), flow1.issuers.in, flow1.issuers.in.issue()); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = issueIOU(leg1.owner(), flow1.order.in, flow1.order.in.issue()); } // leg1 to leg2: bridging over XRP - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = transferXRP(leg1.owner(), leg2.owner(), flow1.order.out); // leg2 to Taker: IOU if (leg2.owner() != account()) { - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = redeemIOU( leg2.owner(), flow2.issuers.out, flow2.issuers.out.issue()); - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = issueIOU(account(), flow2.order.out, flow2.order.out.issue()); } - if (result == tesSUCCESS) + if (isTesSuccess(result)) { bridge_crossings_++; xrp_flow_ += flow1.order.out; diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 0c35154e5..f3ff68493 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -794,11 +794,11 @@ Transactor::apply() mSourceBalance = mPriorBalance; TER result = consumeSeqProxy(sle); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; result = payFee(); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; if (sle->isFieldPresent(sfAccountTxnID)) @@ -1587,7 +1587,7 @@ Transactor::doTSH( TER tshResult = executeHookChain( tshHook, stateMap, results, tshAccountID, strong, provisionalMeta); - if (canRollback && (tshResult != tesSUCCESS)) + if (canRollback && (!isTesSuccess(tshResult))) return tshResult; } @@ -1738,7 +1738,7 @@ Transactor::operator()() // Pre-application (Strong TSH) Hooks are executed here // These TSH have the right to rollback. // Weak TSH and callback are executed post-application. - if (hooksEnabled && result == tesSUCCESS) + if (hooksEnabled && isTesSuccess(result)) { // this state map will be shared across all hooks in this execution // chain and any associated chains which are executed during this @@ -1789,7 +1789,7 @@ Transactor::operator()() } // fall through allows normal apply - if (result == tesSUCCESS) + if (isTesSuccess(result)) result = apply(); // No transaction can return temUNKNOWN from apply, diff --git a/src/ripple/app/tx/impl/URIToken.cpp b/src/ripple/app/tx/impl/URIToken.cpp index 741019923..bcb57f9eb 100644 --- a/src/ripple/app/tx/impl/URIToken.cpp +++ b/src/ripple/app/tx/impl/URIToken.cpp @@ -516,7 +516,7 @@ URIToken::doApply() // IOU sale if (TER result = trustTransferAllowed( sb, {account_, *owner}, purchaseAmount.issue(), j); - result != tesSUCCESS) + !isTesSuccess(result)) { JLOG(j.trace()) << "URIToken::doApply trustTransferAllowed result=" @@ -534,7 +534,7 @@ URIToken::doApply() // execute the funds transfer, we'll check reserves last if (TER result = accountSend( sb, account_, *owner, purchaseAmount, j, false); - result != tesSUCCESS) + !isTesSuccess(result)) return result; // add token to new owner dir diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index d94d2c0dc..b1708e0b1 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -194,22 +194,22 @@ invoke_preclaim(PreclaimContext const& ctx) { TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; result = T::checkPriorTxAndLastLedger(ctx); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; result = T::checkSign(ctx); - if (result != tesSUCCESS) + if (!isTesSuccess(result)) return result; } @@ -630,7 +630,7 @@ preclaim( try { #endif - if (ctx->preflightResult != tesSUCCESS) + if (!isTesSuccess(ctx->preflightResult)) return {*ctx, ctx->preflightResult}; return {*ctx, invoke_preclaim(*ctx)}; #ifndef DEBUG diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 060e8d1ab..42173982c 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -1170,7 +1170,8 @@ rippleSend( // Direct send: redeeming IOUs and/or sending own IOUs. auto const ter = rippleCredit(view, uSenderID, uReceiverID, saAmount, false, j); - if (view.rules().enabled(featureDeletableAccounts) && ter != tesSUCCESS) + if (view.rules().enabled(featureDeletableAccounts) && + !isTesSuccess(ter)) return ter; saActual = saAmount; return tesSUCCESS; @@ -1202,7 +1203,7 @@ rippleSend( TER terResult = rippleCredit(view, issuer, uReceiverID, destReceives, true, j); - if (tesSUCCESS == terResult) + if (isTesSuccess(terResult)) terResult = rippleCredit(view, uSenderID, issuer, senderPays, true, j); return terResult; @@ -1292,7 +1293,7 @@ accountSend( } } - if (tesSUCCESS == terResult && receiver) + if (isTesSuccess(terResult) && receiver) { // Increment XRP balance. auto const rcvBal = receiver->getFieldAmount(sfBalance); diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 74ea0ae1c..2d46df876 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 69; +static constexpr std::size_t numFeatures = 70; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -357,6 +357,7 @@ extern uint256 const fixXahauV1; extern uint256 const fixXahauV2; extern uint256 const featureRemit; extern uint256 const featureZeroB2M; +extern uint256 const fixNSDelete; } // namespace ripple diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 750c58133..42d3cabd3 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -240,7 +240,8 @@ enum TEScodes : TERUnderlyingType { // Implications: // - Applied // - Forwarded - tesSUCCESS = 0 + tesSUCCESS = 0, + tesPARTIAL = 1, }; //------------------------------------------------------------------------------ @@ -436,7 +437,7 @@ public: // Conversion to bool. explicit operator bool() const { - return code_ != tesSUCCESS; + return code_ < tesSUCCESS || code_ >= tecCLAIM; } // Conversion to Json::Value allows assignment to Json::Objects @@ -641,10 +642,17 @@ isTerRetry(TER x) return ((x) >= terRETRY && (x) < tesSUCCESS); } +template +inline bool +isTesSuccess(T x) +{ + return ((x) >= tesSUCCESS) && (x) < tecCLAIM; +} + inline bool isTesSuccess(TER x) { - return ((x) == tesSUCCESS); + return ((x) >= tesSUCCESS) && (x) < tecCLAIM; } inline bool diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index bd23d3e58..9cf82e316 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -463,6 +463,7 @@ REGISTER_FIX (fixXahauV1, Supported::yes, VoteBehavior::De REGISTER_FIX (fixXahauV2, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo); +REGISTER_FIX (fixNSDelete, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index b7bc39186..bc8a41fa9 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -192,6 +192,7 @@ transResults() MAKE_ERROR(terNO_HOOK, "No hook with that hash exists on the ledger."), MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."), + MAKE_ERROR(tesPARTIAL, "The transaction was applied but should be submitted again until returning tesSUCCESS."), }; // clang-format on diff --git a/src/ripple/rpc/impl/DeliveredAmount.cpp b/src/ripple/rpc/impl/DeliveredAmount.cpp index 5e3254f08..1ad5e7210 100644 --- a/src/ripple/rpc/impl/DeliveredAmount.cpp +++ b/src/ripple/rpc/impl/DeliveredAmount.cpp @@ -104,7 +104,7 @@ canHaveDeliveredAmountHelp( } // if the transaction failed nothing could have been delivered. - if (transactionMeta.getResultTER() != tesSUCCESS) + if (!isTesSuccess(transactionMeta.getResultTER())) return false; return true; diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 11dcb02eb..79d47fbaf 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -26,8 +26,6 @@ #include #include -// RH TODO: test collect calls / weak tsh - namespace ripple { namespace test { @@ -689,6 +687,8 @@ public: using namespace jtx; Env env{*this, features}; + bool const fixNS = env.current()->rules().enabled(fixNSDelete); + auto const alice = Account{"alice"}; env.fund(XRP(10000), alice); @@ -770,6 +770,8 @@ public: BEAST_EXPECT( data[0] == 'v' && data[1] == 'a' && data[2] == 'l' && data[3] == 'u' && data[4] == 'e' && data[5] == '\0'); + + BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2); } // delete the namespace @@ -798,6 +800,131 @@ public: // ensure the state object is gone BEAST_EXPECT(!env.le(stateKeylet)); + BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == fixNS ? 1 : 2); + } + } + + void + testNSDeletePartial(FeatureBitset features) + { + testcase("Checks partial nsdelete operation"); + using namespace jtx; + + static const std::vector hook = { + 0x00U, 0x61U, 0x73U, 0x6dU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, + 0x21U, 0x05U, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7fU, 0x60U, + 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x03U, 0x7fU, 0x7fU, + 0x7eU, 0x01U, 0x7eU, 0x60U, 0x04U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, + 0x01U, 0x7eU, 0x60U, 0x01U, 0x7fU, 0x01U, 0x7eU, 0x02U, 0x55U, + 0x06U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x02U, 0x5fU, 0x67U, 0x00U, + 0x00U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x0cU, 0x68U, 0x6fU, 0x6fU, + 0x6bU, 0x5fU, 0x61U, 0x63U, 0x63U, 0x6fU, 0x75U, 0x6eU, 0x74U, + 0x00U, 0x01U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x08U, 0x72U, 0x6fU, + 0x6cU, 0x6cU, 0x62U, 0x61U, 0x63U, 0x6bU, 0x00U, 0x02U, 0x03U, + 0x65U, 0x6eU, 0x76U, 0x05U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, + 0x00U, 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x09U, 0x73U, 0x74U, + 0x61U, 0x74U, 0x65U, 0x5fU, 0x73U, 0x65U, 0x74U, 0x00U, 0x03U, + 0x03U, 0x65U, 0x6eU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, + 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x04U, 0x05U, + 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x2bU, 0x07U, 0x7fU, 0x01U, + 0x41U, 0x80U, 0x88U, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, + 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, + 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, + 0x88U, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x00U, 0x0bU, 0x7fU, + 0x00U, 0x41U, 0x01U, 0x0bU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, + 0x6fU, 0x6fU, 0x6bU, 0x00U, 0x06U, 0x0aU, 0x94U, 0x81U, 0x00U, + 0x01U, 0x90U, 0x81U, 0x00U, 0x02U, 0x02U, 0x7fU, 0x01U, 0x7eU, + 0x23U, 0x00U, 0x41U, 0xd0U, 0x00U, 0x6bU, 0x22U, 0x01U, 0x24U, + 0x00U, 0x20U, 0x01U, 0x20U, 0x00U, 0x36U, 0x02U, 0x4cU, 0x41U, + 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1aU, 0x20U, 0x01U, 0x41U, + 0x30U, 0x6aU, 0x41U, 0x14U, 0x10U, 0x01U, 0x42U, 0x14U, 0x52U, + 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x0cU, 0x10U, + 0x02U, 0x1aU, 0x0bU, 0x20U, 0x01U, 0x41U, 0x28U, 0x6aU, 0x22U, + 0x00U, 0x41U, 0x08U, 0x20U, 0x01U, 0x41U, 0x30U, 0x6aU, 0x22U, + 0x02U, 0x41U, 0x14U, 0x10U, 0x03U, 0x1aU, 0x20U, 0x01U, 0x20U, + 0x01U, 0x29U, 0x03U, 0x28U, 0x42U, 0x01U, 0x7cU, 0x37U, 0x03U, + 0x28U, 0x20U, 0x01U, 0x41U, 0xabU, 0x01U, 0x3aU, 0x00U, 0x09U, + 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x28U, 0x3cU, 0x00U, + 0x0aU, 0x20U, 0x00U, 0x41U, 0x08U, 0x20U, 0x02U, 0x41U, 0x14U, + 0x10U, 0x04U, 0x1aU, 0x20U, 0x00U, 0x41U, 0x08U, 0x20U, 0x01U, + 0x41U, 0x20U, 0x10U, 0x04U, 0x1aU, 0x41U, 0x00U, 0x41U, 0x00U, + 0x42U, 0x00U, 0x10U, 0x05U, 0x20U, 0x01U, 0x41U, 0xd0U, 0x00U, + 0x6aU, 0x24U, 0x00U, 0x0bU}; + + Env env{*this, features}; + bool const fixNS = env.current()->rules().enabled(fixNSDelete); + + auto const bob = Account{"bob"}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000000), alice); + env.fund(XRP(10000000), bob); + + // install the hook on alice + env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + M("set state_set_max"), + HSFEE); + env.close(); + + // invoke the hook + for (uint32_t i = 0; i < 256; ++i) + { + env(pay(bob, alice, XRP(1)), M("test state_set_max"), fee(XRP(1))); + env.close(); + } + + BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 258); + + // delete the namespace pass 1 + { + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + Json::Value iv; + iv[jss::Flags] = hsfNSDELETE; + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, HSFEE, ter(fixNS ? tesPARTIAL : tesSUCCESS)); + env.close(); + + // ensure the directory is still there + auto const dirKeylet = keylet::hookStateDir( + Account("alice").id(), uint256{beast::zero}); + if (fixNS) + { + BEAST_EXPECT(env.le(dirKeylet)); + BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2); + } + else + { + BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 258); + BEAST_EXPECT(!env.le(dirKeylet)); + } + } + + // delete the namespace pass 2 + if (fixNS) + { + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + Json::Value iv; + iv[jss::Flags] = hsfNSDELETE; + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, HSFEE, ter(tesSUCCESS)); + env.close(); + + // ensure the directory is gone + auto const dirKeylet = keylet::hookStateDir( + Account("alice").id(), uint256{beast::zero}); + BEAST_EXPECT(!env.le(dirKeylet)); + + // ensure the owner count is 1 + BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 1); } } @@ -11633,6 +11760,7 @@ public: testUpdate(features); testNSDelete(features); + testNSDeletePartial(features); testWasm(features); test_accept(features); @@ -11733,6 +11861,7 @@ public: testWithFeatures(sa); testWithFeatures(sa - fixXahauV2); testWithFeatures(sa - fixXahauV1 - fixXahauV2); + testWithFeatures(sa - fixXahauV1 - fixXahauV2 - fixNSDelete); } private: