diff --git a/src/ripple/app/tx/impl/ClaimReward.cpp b/src/ripple/app/tx/impl/ClaimReward.cpp index 4cc9abb25..d92b10bdc 100644 --- a/src/ripple/app/tx/impl/ClaimReward.cpp +++ b/src/ripple/app/tx/impl/ClaimReward.cpp @@ -41,6 +41,11 @@ ClaimReward::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + // can have flag 1 set to opt-out of rewards + if (ctx.tx.isFieldPresent(sfFlags) && + ctx.tx.getFieldU32(sfFlags) > 1) + return temMALFORMED; + return preflight2(ctx); } @@ -56,8 +61,15 @@ ClaimReward::preclaim(PreclaimContext const& ctx) if (!sle) return terNO_ACCOUNT; - auto const issuer = ctx.tx[sfIssuer]; - if (!ctx.view.exists(keylet::account(issuer))) + std::optional flags = ctx.tx[~sfFlags]; + std::optional issuer = ctx.tx[~sfIssuer]; + + bool isOptOut = flags && *flags == 1; + + if ((issuer && isOptOut) || (!issuer && !isOptOut)) + return temMALFORMED; + + if (issuer && !ctx.view.exists(keylet::account(*issuer))) return tecNO_ISSUER; return tesSUCCESS; @@ -69,16 +81,32 @@ ClaimReward::doApply() auto const sle = view().peek(keylet::account(account_)); if (!sle) return tefINTERNAL; + + std::optional flags = ctx_.tx[~sfFlags]; + std::optional issuer = ctx_.tx[~sfIssuer]; - // all actual rewards are handled by the hook on the sfIssuer - // the tt just resets the counters - uint32_t lgrCur = view().seq(); - sle->setFieldU32(sfRewardLgrFirst, lgrCur); - sle->setFieldU32(sfRewardLgrLast, lgrCur); - sle->setFieldU64(sfRewardAccumulator, 0ULL); + bool isOptOut = flags && *flags == 1; + + if (isOptOut) + { + if (sle->isFieldPresent(sfRewardLgrFirst)) + sle->makeFieldAbsent(sfRewardLgrFirst); + if (sle->isFieldPresent(sfRewardLgrLast)) + sle->makeFieldAbsent(sfRewardLgrLast); + if (sle->isFieldPresent(sfRewardAccumulator)) + sle->makeFieldAbsent(sfRewardAccumulator); + } + else + { + // all actual rewards are handled by the hook on the sfIssuer + // the tt just resets the counters + uint32_t lgrCur = view().seq(); + sle->setFieldU32(sfRewardLgrFirst, lgrCur); + sle->setFieldU32(sfRewardLgrLast, lgrCur); + sle->setFieldU64(sfRewardAccumulator, 0ULL); + } view().update(sle); - return tesSUCCESS; } diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index f8fc1d876..63b404b8a 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -584,34 +584,6 @@ Transactor::apply() if (sle->isFieldPresent(sfAccountTxnID)) sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID()); - // accumulate rewards if the BalanceRewards amendment is enabled - if (view().rules().enabled(featureBalanceRewards) && - ctx_.tx[sfTransactionType] != ttCLAIM_REWARD) - { - uint32_t lgrCur = view().seq(); - if (!sle->isFieldPresent(sfRewardLgrFirst) || - !sle->isFieldPresent(sfRewardLgrLast) || - !sle->isFieldPresent(sfRewardAccumulator)) - { - - sle->setFieldU32(sfRewardLgrFirst, lgrCur); - sle->setFieldU32(sfRewardLgrLast, lgrCur); - sle->setFieldU64(sfRewardAccumulator, 0ULL); - } - else - { - uint64_t bal = mPriorBalance.drops(); - uint32_t lgrLast = sle->getFieldU32(sfRewardLgrLast); - if (lgrCur - lgrLast != 0) - { - uint64_t accum = sle->getFieldU64(sfRewardAccumulator); - accum += bal * (lgrCur - lgrLast); - sle->setFieldU64(sfRewardAccumulator, accum); - sle->setFieldU32(sfRewardLgrLast, lgrCur); - } - } - } - view().update(sle); } @@ -1701,6 +1673,73 @@ Transactor::operator()() if (!isTecClaim(result) && !isTesSuccess(result)) applied = false; } + + if (applied && view().rules().enabled(featureBalanceRewards)) + { + TxMeta metaRaw = ctx_.generateProvisionalMeta(); + metaRaw.setResult(result, 0); + STObject const meta = std::move(metaRaw.getAsObject()); + + uint32_t lgrCur = view().seq(); + // iterate all affected balances + for (auto const& node : meta.getFieldArray(sfAffectedNodes)) + { + SField const& metaType = node.getFName(); + uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); + + // we only care about ltACCOUNT_ROOT objects being modified or + // created + if (nodeType != ltACCOUNT_ROOT || metaType == sfDeletedNode) + continue; + + if (!node.isFieldPresent(sfFinalFields) || + !node.isFieldPresent(sfLedgerIndex)) + continue; + + auto sle = view().peek(Keylet{ ltACCOUNT_ROOT, node.getFieldH256(sfLedgerIndex) }); + + if (!sle) + continue; + + if (!sle->isFieldPresent(sfRewardLgrFirst) || + !sle->isFieldPresent(sfRewardLgrLast) || + !sle->isFieldPresent(sfRewardAccumulator)) + continue; + + STObject& finalFields = (const_cast(node)) + .getField(sfFinalFields) + .downcast(); + + if (!finalFields.isFieldPresent(sfBalance)) + continue; + + + uint64_t bal = finalFields.getFieldAmount(sfBalance).xrp().drops() / 1'000'000; + + if (bal == 0) + continue; + + uint32_t lgrLast = sle->getFieldU32(sfRewardLgrLast); + + uint32_t lgrElapsed = lgrCur - lgrLast; + + // overflow safety + if (lgrElapsed > lgrCur || lgrElapsed > lgrLast || lgrElapsed == 0) + continue; + + uint64_t accum = sle->getFieldU64(sfRewardAccumulator); + uint64_t accumNew = accum + bal * ((uint64_t)lgrElapsed); + + // check for overflow + if (accumNew < accum) + continue; + + sle->setFieldU64(sfRewardAccumulator, accumNew); + sle->setFieldU32(sfRewardLgrLast, lgrCur); + + view().update(sle); + } + } // Post-application (Weak TSH/AAW) Hooks are executed here. // These TSH do not have the ability to rollback. @@ -1742,7 +1781,6 @@ Transactor::operator()() } - if (applied) { // Transaction succeeded fully or (retries are not allowed and the