diff --git a/src/ripple/app/tx/applyHook.h b/src/ripple/app/tx/applyHook.h index d03102aeb..46001d5b5 100644 --- a/src/ripple/app/tx/applyHook.h +++ b/src/ripple/app/tx/applyHook.h @@ -511,7 +511,7 @@ namespace hook std::vector, std::vector >> const& hookParamOverrides, - std::shared_ptr& stateMap, + HookStateMap& stateMap, ripple::ApplyContext& applyCtx, ripple::AccountID const& account, /* the account the hook is INSTALLED ON not always the otxn account */ bool callback = false, @@ -620,7 +620,7 @@ namespace hook // write state map to ledger ripple::TER finalizeHookState( - std::shared_ptr&, + HookStateMap const&, ripple::ApplyContext&, ripple::uint256 const&); @@ -629,6 +629,13 @@ namespace hook removeEmissionEntry( ripple::ApplyContext& applyCtx); + bool /* retval of true means an error */ + gatherHookParameters( + std::shared_ptr const& hookDef, + ripple::STObject const* hookObj, + std::map, std::vector>& parameters, + beast::Journal const& j_); + // RH TODO: call destruct for these on rippled shutdown #define ADD_HOOK_FUNCTION(F, ctx)\ {\ diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp index 833d18d88..93b45be3d 100644 --- a/src/ripple/app/tx/impl/CreateOffer.cpp +++ b/src/ripple/app/tx/impl/CreateOffer.cpp @@ -1227,7 +1227,10 @@ CreateOffer::doApply() auto const result = applyGuts(sb, sbCancel); if (result.second) + { sb.apply(ctx_.rawView()); + addWeakTSHFromSandbox(sb); + } else sbCancel.apply(ctx_.rawView()); return result.first; diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp index 50045da8d..11d0730d6 100644 --- a/src/ripple/app/tx/impl/Payment.cpp +++ b/src/ripple/app/tx/impl/Payment.cpp @@ -412,6 +412,7 @@ Payment::doApply() // on the TER. But always applying *should* // be safe. pv.apply(ctx_.rawView()); + addWeakTSHFromSandbox(pv); } // TODO: is this right? If the amount is the correct amount, was diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index c4c0d5f0a..75429298a 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -33,14 +33,9 @@ #include #include #include +#include #include - - -//RH TODO: remove after debugging -#include -#include -#include -//-------- +#include namespace ripple { @@ -156,7 +151,11 @@ Transactor::Transactor(ApplyContext& ctx) // RH NOTE: this only computes one chain at a time, so if there is a receiving side to a txn // then it must seperately be computed by a second call here FeeUnit64 -Transactor::calculateHookChainFee(ReadView const& view, STTx const& tx, Keylet const& hookKeylet) +Transactor:: +calculateHookChainFee( + ReadView const& view, + STTx const& tx, + Keylet const& hookKeylet) { std::shared_ptr hookSLE = view.read(hookKeylet); @@ -889,60 +888,13 @@ Transactor::reset(XRPAmount fee) return {ter, fee}; } -static __inline__ unsigned long long rdtsc(void) -{ - unsigned hi, lo; - __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi)); - return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 ); -} - -bool /* retval of true means an error */ -gatherHookParameters( - std::shared_ptr const& hookDef, - ripple::STObject const* hookObj, - std::map, std::vector>& parameters, - beast::Journal const& j_) -{ - if (!hookDef->isFieldPresent(sfHookParameters)) - { - JLOG(j_.fatal()) - << "HookError[]: Failure: hook def missing parameters (send)"; - return true; - } - - // first defaults - auto const& defaultParameters = hookDef->getFieldArray(sfHookParameters); - for (auto const& hookParameter : defaultParameters) - { - auto const& hookParameterObj = dynamic_cast(&hookParameter); - parameters[hookParameterObj->getFieldVL(sfHookParameterName)] = - hookParameterObj->getFieldVL(sfHookParameterValue); - } - - // and then custom - if (hookObj->isFieldPresent(sfHookParameters)) - { - auto const& hookParameters = hookObj->getFieldArray(sfHookParameters); - for (auto const& hookParameter : hookParameters) - { - auto const& hookParameterObj = dynamic_cast(&hookParameter); - parameters[hookParameterObj->getFieldVL(sfHookParameterName)] = - hookParameterObj->getFieldVL(sfHookParameterValue); - } - } - return false; -} - -bool /* retval of true means an error */ +TER +Transactor:: executeHookChain( - std::shared_ptr const& hookSLE, - std::shared_ptr& stateMap, - std::vector& results, - int& executedHookCount, - ripple::AccountID const& account, - ripple::ApplyContext& ctx, - beast::Journal const& j_, - TER& result) + std::shared_ptr const& hookSLE, + hook::HookStateMap& stateMap, + std::vector& results, + ripple::AccountID const& account) { std::set hookSkips; std::map< @@ -972,12 +924,11 @@ executeHookChain( continue; } - auto const& hookDef = ctx.view().peek(keylet::hookDefinition(hookHash)); + auto const& hookDef = ctx_.view().peek(keylet::hookDefinition(hookHash)); if (!hookDef) { JLOG(j_.warn()) << "HookError[]: Failure: hook def missing (send)"; -// return true; continue; } @@ -986,7 +937,7 @@ executeHookChain( ? hookObj->getFieldU64(sfHookOn) : hookDef->getFieldU64(sfHookOn)); - if (!hook::canHook(ctx.tx.getTxnType(), hookOn)) + if (!hook::canHook(ctx_.tx.getTxnType(), hookOn)) continue; // skip if it can't // fetch the namespace either from the hook object of, if absent, the hook def @@ -997,11 +948,11 @@ executeHookChain( // gather parameters std::map, std::vector> parameters; - if (gatherHookParameters(hookDef, hookObj, parameters, j_)) + if (hook::gatherHookParameters(hookDef, hookObj, parameters, j_)) { JLOG(j_.warn()) << "HookError[]: Failure: gatherHookParameters failed)"; - return true; + return tecINTERNAL; } results.push_back( @@ -1013,21 +964,21 @@ executeHookChain( parameters, hookParamOverrides, stateMap, - ctx, + ctx_, account, false, 0, hook_no)); - executedHookCount++; + executedHookCount_++; hook::HookResult& hookResult = results.back(); if (hookResult.exitType != hook_api::ExitType::ACCEPT) { if (results.back().exitType == hook_api::ExitType::WASM_ERROR) - result = temMALFORMED; - return true; + return temMALFORMED; + return tecHOOK_REJECTED; } // gather skips @@ -1049,10 +1000,298 @@ executeHookChain( hook_no++; } - return false; + return tesSUCCESS; } +void +Transactor::doHookCallback() +{ + // Finally check if there is a callback + if (!ctx_.tx.isFieldPresent(sfEmitDetails)) + return; + auto const& emitDetails = + const_cast(ctx_.tx).getField(sfEmitDetails).downcast(); + + // callbacks are optional so if there isn't a callback then skip + if (!emitDetails.isFieldPresent(sfEmitCallback)) + return; + + AccountID const& callbackAccountID = emitDetails.getAccountID(sfEmitCallback); + uint256 const& callbackHookHash = emitDetails.getFieldH256(sfEmitHookHash); + + auto const& hooksCallback = view().peek(keylet::hook(callbackAccountID)); + auto const& hookDef = view().peek(keylet::hookDefinition(callbackHookHash)); + if (!hookDef) + { + JLOG(j_.warn()) + << "HookError[]: Hook def missing on callback"; + return; + } + + if (!hookDef->isFieldPresent(sfHookCallbackFee)) + { + JLOG(j_.trace()) + << "HookInfo[" << callbackAccountID << "]: Callback specified by emitted txn " + << "but hook lacks a cbak function, skipping."; + return; + } + + if (!hooksCallback) + { + JLOG(j_.warn()) + << "HookError[]: Hook missing on callback"; + return; + } + + if (!hooksCallback->isFieldPresent(sfHooks)) + { + JLOG(j_.warn()) + << "HookError[]: Hooks Array missing on callback"; + return; + } + + bool found = false; + auto const& hooks = hooksCallback->getFieldArray(sfHooks); + int hook_no = 0; + for (auto const& hook : hooks) + { + hook_no++; + + STObject const* hookObj = dynamic_cast(&hook); + + if (!hookObj->isFieldPresent(sfHookHash)) // skip blanks + continue; + + if (hookObj->getFieldH256(sfHookHash) != callbackHookHash) + continue; + + // fetch the namespace either from the hook object of, if absent, the hook def + uint256 const& ns = + (hookObj->isFieldPresent(sfHookNamespace) + ? hookObj->getFieldH256(sfHookNamespace) + : hookDef->getFieldH256(sfHookNamespace)); + + executedHookCount_++; + + std::map, std::vector> parameters; + if (hook::gatherHookParameters(hookDef, hookObj, parameters, j_)) + { + JLOG(j_.warn()) + << "HookError[]: Failure: gatherHookParameters failed)"; + return; + } + + found = true; + + // this call will clean up ltEMITTED_NODE as well + try + { + + hook::HookStateMap stateMap; + + hook::HookResult callbackResult = + hook::apply( + hookDef->getFieldH256(sfHookSetTxnID), + callbackHookHash, + ns, + hookDef->getFieldVL(sfCreateCode), + parameters, + {}, + stateMap, + ctx_, + callbackAccountID, + true, + safe_cast(ctx_.tx.getFieldU16(sfTransactionType)) == ttEMIT_FAILURE + ? 1UL : 0UL, + hook_no - 1); + + + bool success = callbackResult.exitType == hook_api::ExitType::ACCEPT; + + // write any state changes if cbak resulted in accept() + if (success) + hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID()); + + // write the final result + ripple::TER result = + finalizeHookResult(callbackResult, ctx_, success); + + JLOG(j_.trace()) + << "HookInfo[" << callbackAccountID << "-" < const& tpl = entry.first; + Currency const& cur = std::get<2>(tpl); + if (isXRP(cur)) + continue; + + AccountID const& lowAcc = std::get<0>(tpl); + AccountID const& highAcc = std::get<1>(tpl); + STAmount const& amt = entry.second; + additionalWeakTSH_.emplace(amt >= beast::zero ? lowAcc : highAcc); + } + } +} + +TER +Transactor:: +doTSH( + bool strong, // only strong iff true, only weak iff false + hook::HookStateMap& stateMap, + std::vector& results) +{ + auto& view = ctx_.view(); + + std::vector> tsh = + hook::getTransactionalStakeHolders(ctx_.tx, view); + + // add the extra TSH marked out by the specific transactor (if applicable) + if (!strong) + for (auto& weakTsh : additionalWeakTSH_) + tsh.emplace_back(weakTsh, false); + + // we use a vector above for order preservation + // but we also don't want to execute any hooks + // twice, so keep track as we go with a map + std::set alreadyProcessed; + + for (auto& [tshAccountID, canRollback] : tsh) + { + // this isn't an error because transactors may + // blindly nominate any TSHes they find but + // obviously we will never execute OTXN account + // as a TSH because they already had first execution + if (tshAccountID == account_) + continue; + + if (alreadyProcessed.find(tshAccountID) != alreadyProcessed.end()) + continue; + + alreadyProcessed.emplace(tshAccountID); + + // only process the relevant ones + if ((!canRollback && strong) || (canRollback && !strong)) + continue; + + auto klTshHook = keylet::hook(tshAccountID); + + auto tshHook = view.read(klTshHook); + if (!(tshHook && tshHook->isFieldPresent(sfHooks))) + continue; + + // scoping here allows tshAcc to leave scope before + // hook execution, which is probably safer + { + // check if the TSH exists and/or has any hooks + auto tshAcc = view.peek(keylet::account(tshAccountID)); + if (!tshAcc) + continue; + + // compute and deduct fees for the TSH if applicable + FeeUnit64 tshFee = + calculateHookChainFee(view, ctx_.tx, klTshHook); + + // no hooks to execute, skip tsh + if (tshFee == 0) + continue; + + XRPAmount tshFeeDrops = view.fees().toDrops(tshFee); + assert(tshFeeDrops >= beast::zero); + + STAmount priorBalance = tshAcc->getFieldAmount(sfBalance); + + if (canRollback) + { + // this is not a collect call so we will force the tsh's fee to 0 + // the otxn paid the fee for this tsh chain execution already. + tshFeeDrops = 0; + } + else + { + // this is a collect call so first check if the tsh can accept + uint32_t tshFlags = tshAcc->getFieldU32(sfFlags); + if (!canRollback && !(tshFlags & lsfTshCollect)) + { + // this TSH doesn't allow collect calls, skip + JLOG(j_.trace()) + << "HookInfo[" << account_ << "]: TSH acc " << tshAccountID << " " + << "hook chain execution skipped due to lack of lsfTshCollect flag."; + continue; + } + + // now check if they can afford this collect call + auto const uOwnerCount = tshAcc->getFieldU32(sfOwnerCount); + auto const reserve = view.fees().accountReserve(uOwnerCount); + + if (tshFeeDrops + reserve > priorBalance) + { + JLOG(j_.trace()) + << "HookInfo[" << account_ << "]: TSH acc " << tshAccountID << " " + << "hook chain execution skipped due to lack of TSH acc funds."; + continue; + } + } + + if (tshFeeDrops > beast::zero) + { + STAmount finalBalance = priorBalance -= tshFeeDrops; + assert(finalBalance >= beast::zero); + assert(finalBalance < priorBalance); + + tshAcc->setFieldAmount(sfBalance, finalBalance); + view.update(tshAcc); + ctx_.destroyXRP(tshFeeDrops); + } + } + + // execution to here means we can run the TSH's hook chain + TER tshResult = + executeHookChain( + tshHook, + stateMap, + results, + tshAccountID); + + if (canRollback && (tshResult != tesSUCCESS)) + return tshResult; + } + + return tesSUCCESS; +} //------------------------------------------------------------------------------ std::pair @@ -1081,287 +1320,63 @@ Transactor::operator()() auto result = ctx_.preclaimResult; + + bool const hooksEnabled = + ctx_.view().rules().enabled(featureHooks); - int executedHookCount = 0; - bool rollback = false; - uint64_t prehook_cycles = rdtsc(); - - - if (ctx_.view().rules().enabled(featureHooks) && - (result == tesSUCCESS || result == tecHOOK_REJECTED)) + // 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 || result == tecHOOK_REJECTED)) { - // this state map will be shared across all hooks in this execution chain // and any associated chains which are executed during this transaction also - std::shared_ptr stateMap = std::make_shared(); + // this map can get large so + hook::HookStateMap stateMap; + + bool rollback = false; auto& view = ctx_.view(); auto const& accountID = ctx_.tx.getAccountID(sfAccount); - std::vector sendResults; - std::vector recvResults; + std::vector orgResults; + std::vector tshResults; - auto const& hooksSending = view.read(keylet::hook(accountID)); + auto const& hooksOriginator = view.read(keylet::hook(accountID)); // First check if the Sending account has any hooks that can be fired - if (hooksSending && hooksSending->isFieldPresent(sfHooks) && !ctx_.emitted()) - rollback = executeHookChain(hooksSending, stateMap, - sendResults, executedHookCount, accountID, ctx_, j_, result); + if (hooksOriginator && hooksOriginator->isFieldPresent(sfHooks) && !ctx_.emitted()) + result = + executeHookChain( + hooksOriginator, + stateMap, + orgResults, + accountID); - // Next check if there are any transactional stake holders whose hooks need to be executed - if (!rollback) + if (isTesSuccess(result)) { - std::vector> tsh = - hook::getTransactionalStakeHolders(ctx_.tx, view); + // Next check if there are any transactional stake holders whose hooks need to be executed + // here. Note these are only strong TSH (who have the right to rollback the txn), + // any weak TSH will be executed after doApply has been successful (callback as well) - for (auto& [tshAccountID, canRollback] : tsh) - { - auto klTshHook = keylet::hook(tshAccountID); - - auto tshHook = view.read(klTshHook); - if (!(tshHook && tshHook->isFieldPresent(sfHooks))) - continue; - - // scoping here allows tshAcc to leave scope before - // hook execution, which is probably safer - { - // check if the TSH exists and/or has any hooks - auto tshAcc = view.peek(keylet::account(tshAccountID)); - if (!tshAcc) - continue; - - - // compute and deduct fees for the TSH if applicable - FeeUnit64 tshFee = - calculateHookChainFee(view, ctx_.tx, klTshHook); - - // no hooks to execute, skip tsh - if (tshFee == 0) - continue; - - XRPAmount tshFeeDrops = view.fees().toDrops(tshFee); - assert(tshFeeDrops >= beast::zero); - - STAmount priorBalance = tshAcc->getFieldAmount(sfBalance); - - if (canRollback) - { - // this is not a collect call so we will force the tsh's fee to 0 - // the otxn paid the fee for this tsh chain execution already. - tshFeeDrops = 0; - } - else - { - // this is a collect call so first check if the tsh can accept - uint32_t tshFlags = tshAcc->getFieldU32(sfFlags); - if (!canRollback && !(tshFlags & lsfTshCollect)) - { - // this TSH doesn't allow collect calls, skip - JLOG(j_.trace()) - << "HookInfo[" << accountID << "]: TSH acc " << tshAccountID << " " - << "hook chain execution skipped due to lack of lsfTshCollect flag."; - continue; - } - - // now check if they can afford this collect call - auto const uOwnerCount = tshAcc->getFieldU32(sfOwnerCount); - auto const reserve = view.fees().accountReserve(uOwnerCount); - - if (tshFeeDrops + reserve > priorBalance) - { - JLOG(j_.trace()) - << "HookInfo[" << accountID << "]: TSH acc " << tshAccountID << " " - << "hook chain execution skipped due to lack of TSH acc funds."; - continue; - } - } - - if (tshFeeDrops > beast::zero) - { - STAmount finalBalance = priorBalance -= tshFeeDrops; - assert(finalBalance >= beast::zero); - assert(finalBalance < priorBalance); - - tshAcc->setFieldAmount(sfBalance, finalBalance); - - view.update(tshAcc); - } - } - - // execution to here means we can run the TSH's hook chain - bool tshRollback = - executeHookChain(tshHook, stateMap, - recvResults, executedHookCount, tshAccountID, ctx_, j_, result); - - if (canRollback && tshRollback) - { - rollback = true; - break; - } - } + result = doTSH(true, stateMap, tshResults); } - if (rollback && result != temMALFORMED) - result = tecHOOK_REJECTED; - // write state if all chains executed successfully - if (result == tesSUCCESS) + if (isTesSuccess(result)) hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID()); // write hook results - for (auto& sendResult: sendResults) - hook::finalizeHookResult(sendResult, ctx_, result == tesSUCCESS); - for (auto& recvResult: recvResults) - hook::finalizeHookResult(recvResult, ctx_, result == tesSUCCESS); + // this happens irrespective of whether final result was a tesSUCCESS + // because it contains error codes that any failed hooks would have + // returned for meta + for (auto& orgResult: orgResults) + hook::finalizeHookResult(orgResult, ctx_, isTesSuccess(result)); - // Finally check if there is a callback - do - { - if (ctx_.tx.isFieldPresent(sfEmitDetails) && result == tesSUCCESS) - { - auto const& emitDetails = - const_cast(ctx_.tx).getField(sfEmitDetails).downcast(); - - // callbacks are optional so if there isn't a callback then skip - if (!emitDetails.isFieldPresent(sfEmitCallback)) - break; - - AccountID const& callbackAccountID = emitDetails.getAccountID(sfEmitCallback); - uint256 const& callbackHookHash = emitDetails.getFieldH256(sfEmitHookHash); - - - auto const& hooksCallback = view.peek(keylet::hook(callbackAccountID)); - auto const& hookDef = view.peek(keylet::hookDefinition(callbackHookHash)); - if (!hookDef) - { - JLOG(j_.warn()) - << "HookError[]: Hook def missing on callback"; - break; - } - - if (!hookDef->isFieldPresent(sfHookCallbackFee)) - { - JLOG(j_.trace()) - << "HookInfo[" << callbackAccountID << "]: Callback specified by emitted txn " - << "but hook lacks a cbak function, skipping."; - break; - } - - if (!hooksCallback) - { - JLOG(j_.warn()) - << "HookError[]: Hook missing on callback"; - break; - } - - if (!hooksCallback->isFieldPresent(sfHooks)) - { - JLOG(j_.warn()) - << "HookError[]: Hooks Array missing on callback"; - break; - } - - bool found = false; - auto const& hooks = hooksCallback->getFieldArray(sfHooks); - int hook_no = 0; - for (auto const& hook : hooks) - { - hook_no++; - - STObject const* hookObj = dynamic_cast(&hook); - - if (!hookObj->isFieldPresent(sfHookHash)) // skip blanks - continue; - - if (hookObj->getFieldH256(sfHookHash) != callbackHookHash) - continue; - - // fetch the namespace either from the hook object of, if absent, the hook def - uint256 const& ns = - (hookObj->isFieldPresent(sfHookNamespace) - ? hookObj->getFieldH256(sfHookNamespace) - : hookDef->getFieldH256(sfHookNamespace)); - - executedHookCount++; + for (auto& tshResult: tshResults) + hook::finalizeHookResult(tshResult, ctx_, isTesSuccess(result)); - std::map, std::vector> parameters; - if (gatherHookParameters(hookDef, hookObj, parameters, j_)) - { - JLOG(j_.warn()) - << "HookError[]: Failure: gatherHookParameters failed)"; - break; - } - - found = true; - - // this call will clean up ltEMITTED_NODE as well - try - { - // reset the stateMap - stateMap = std::make_shared(); - - hook::HookResult callbackResult = - hook::apply( - hookDef->getFieldH256(sfHookSetTxnID), - callbackHookHash, - ns, - hookDef->getFieldVL(sfCreateCode), - parameters, - {}, - stateMap, - ctx_, - callbackAccountID, - true, - safe_cast(ctx_.tx.getFieldU16(sfTransactionType)) == ttEMIT_FAILURE - ? 1UL : 0UL, - hook_no - 1); - - - bool success = callbackResult.exitType == hook_api::ExitType::ACCEPT; - - // write any state changes if cbak resulted in accept() - if (result == tesSUCCESS) - hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID()); - - // write the final result - ripple::TER result = - finalizeHookResult(callbackResult, ctx_, success); - - JLOG(j_.trace()) - << "HookInfo[" << callbackAccountID << "-" < tshResults; + + doTSH(false, stateMap, tshResults); + + // write hook results + hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID()); + for (auto& tshResult: tshResults) + hook::finalizeHookResult(tshResult, ctx_, isTesSuccess(result)); + } + + auto fee = ctx_.tx.getFieldAmount(sfFee).xrp(); if (ctx_.size() > oversizeMetaDataCap) diff --git a/src/ripple/app/tx/impl/Transactor.h b/src/ripple/app/tx/impl/Transactor.h index 5c6349ee5..b04f9f671 100644 --- a/src/ripple/app/tx/impl/Transactor.h +++ b/src/ripple/app/tx/impl/Transactor.h @@ -20,10 +20,13 @@ #ifndef RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED #define RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED +#include #include #include #include #include +#include +#include namespace hook { // RH TODO: fix applyHook.h so this prototype isn't needed @@ -107,7 +110,7 @@ protected: public: enum ConsequencesFactoryType { Normal, Blocker, Custom }; - + /** Process the transaction. */ std::pair operator()(); @@ -150,9 +153,6 @@ public: // Returns the fee in fee units, not scaled for load. static FeeUnit64 calculateBaseFee(ReadView const& view, STTx const& tx); - - static FeeUnit64 - calculateHookChainFee(ReadView const& view, STTx const& tx, Keylet const& hookKeylet); // Returns a list of zero or more accounts which are @@ -182,7 +182,42 @@ public: uint256 const& ticketIndex, beast::Journal j); + + // Hooks + + static FeeUnit64 + calculateHookChainFee(ReadView const& view, STTx const& tx, Keylet const& hookKeylet); + protected: + + void + doHookCallback(); + + TER + doTSH( + bool strong, // only do strong TSH iff true, otheriwse only weak + hook::HookStateMap& stateMap, + std::vector& results); + + TER + executeHookChain( + std::shared_ptr const& hookSLE, + hook::HookStateMap& stateMap, + std::vector& results, + ripple::AccountID const& account); + + void + addWeakTSHFromSandbox(ApplyViewBase& pv); + + // hooks amendment fields, these are unpopulated and unused unless featureHooks is enabled + int executedHookCount_ = 0; // record how many hooks have executed across the whole transactor + std::set additionalWeakTSH_; // any TSH that needs weak hook execution at the end + // of the transactor, who isn't able to be deduced until after apply + // i.e. pathing participants, crossed offers + + /////////////////////////////////////////////////// + + TER apply(); diff --git a/src/ripple/app/tx/impl/applyHook.cpp b/src/ripple/app/tx/impl/applyHook.cpp index 57e112fe8..99020590f 100644 --- a/src/ripple/app/tx/impl/applyHook.cpp +++ b/src/ripple/app/tx/impl/applyHook.cpp @@ -1079,7 +1079,7 @@ DEFINE_HOOK_FUNCTION( ripple::TER hook:: finalizeHookState( - std::shared_ptr& stateMap, + HookStateMap const& stateMap, ripple::ApplyContext& applyCtx, ripple::uint256 const& txnID) { @@ -1132,6 +1132,45 @@ finalizeHookState( return tesSUCCESS; } + +bool /* retval of true means an error */ +hook:: +gatherHookParameters( + std::shared_ptr const& hookDef, + ripple::STObject const* hookObj, + std::map, std::vector>& parameters, + beast::Journal const& j_) +{ + if (!hookDef->isFieldPresent(sfHookParameters)) + { + JLOG(j_.fatal()) + << "HookError[]: Failure: hook def missing parameters (send)"; + return true; + } + + // first defaults + auto const& defaultParameters = hookDef->getFieldArray(sfHookParameters); + for (auto const& hookParameter : defaultParameters) + { + auto const& hookParameterObj = dynamic_cast(&hookParameter); + parameters[hookParameterObj->getFieldVL(sfHookParameterName)] = + hookParameterObj->getFieldVL(sfHookParameterValue); + } + + // and then custom + if (hookObj->isFieldPresent(sfHookParameters)) + { + auto const& hookParameters = hookObj->getFieldArray(sfHookParameters); + for (auto const& hookParameter : hookParameters) + { + auto const& hookParameterObj = dynamic_cast(&hookParameter); + parameters[hookParameterObj->getFieldVL(sfHookParameterName)] = + hookParameterObj->getFieldVL(sfHookParameterValue); + } + } + return false; +} + ripple::TER hook:: removeEmissionEntry(ripple::ApplyContext& applyCtx) @@ -1167,9 +1206,9 @@ removeEmissionEntry(ripple::ApplyContext& applyCtx) TER hook:: finalizeHookResult( - hook::HookResult& hookResult, - ripple::ApplyContext& applyCtx, - bool doEmit) + hook::HookResult& hookResult, + ripple::ApplyContext& applyCtx, + bool doEmit) { auto const& j = applyCtx.app.journal("View"); diff --git a/src/ripple/ledger/ApplyViewImpl.h b/src/ripple/ledger/ApplyViewImpl.h index d0e0aec75..6b8328ff1 100644 --- a/src/ripple/ledger/ApplyViewImpl.h +++ b/src/ripple/ledger/ApplyViewImpl.h @@ -116,6 +116,14 @@ public: std::shared_ptr const& before, std::shared_ptr const& after)> const& func); + + // Map of delta trust lines. As a special case, when both ends of the trust + // line are the same currency, then it's delta currency for that issuer. To + // get the change in XRP balance, Account == root, issuer == root, currency + // == XRP + std::map, STAmount> + balanceChanges(ReadView const& view) const; + private: std::optional deliver_; std::vector hookExecution_; diff --git a/src/ripple/ledger/impl/ApplyViewImpl.cpp b/src/ripple/ledger/impl/ApplyViewImpl.cpp index 11e0b7fe0..3db8bd95f 100644 --- a/src/ripple/ledger/impl/ApplyViewImpl.cpp +++ b/src/ripple/ledger/impl/ApplyViewImpl.cpp @@ -52,4 +52,126 @@ ApplyViewImpl::visit( items_.visit(to, func); } +std::map, STAmount> +ApplyView::balanceChanges(ReadView const& view) const +{ + using key_t = std::tuple; + // Map of delta trust lines. As a special case, when both ends of the trust + // line are the same currency, then it's delta currency for that issuer. To + // get the change in XRP balance, Account == root, issuer == root, currency + // == XRP + std::map result; + + // populate a dictionary with low/high/currency/delta. This can be + // compared with the other versions payment code. + auto each = [&result]( + uint256 const& key, + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) { + STAmount oldBalance; + STAmount newBalance; + AccountID lowID; + AccountID highID; + + // before is read from prev view + if (isDelete) + { + if (!before) + return; + + auto const bt = before->getType(); + switch (bt) + { + case ltACCOUNT_ROOT: + lowID = xrpAccount(); + highID = (*before)[sfAccount]; + oldBalance = (*before)[sfBalance]; + newBalance = oldBalance.zeroed(); + break; + case ltRIPPLE_STATE: + lowID = (*before)[sfLowLimit].getIssuer(); + highID = (*before)[sfHighLimit].getIssuer(); + oldBalance = (*before)[sfBalance]; + newBalance = oldBalance.zeroed(); + break; + case ltOFFER: + // TBD + break; + default: + break; + } + } + else if (!before) + { + // insert + auto const at = after->getType(); + switch (at) + { + case ltACCOUNT_ROOT: + lowID = xrpAccount(); + highID = (*after)[sfAccount]; + newBalance = (*after)[sfBalance]; + oldBalance = newBalance.zeroed(); + break; + case ltRIPPLE_STATE: + lowID = (*after)[sfLowLimit].getIssuer(); + highID = (*after)[sfHighLimit].getIssuer(); + newBalance = (*after)[sfBalance]; + oldBalance = newBalance.zeroed(); + break; + case ltOFFER: + // TBD + break; + default: + break; + } + } + else + { + // modify + auto const at = after->getType(); + assert(at == before->getType()); + switch (at) + { + case ltACCOUNT_ROOT: + lowID = xrpAccount(); + highID = (*after)[sfAccount]; + oldBalance = (*before)[sfBalance]; + newBalance = (*after)[sfBalance]; + break; + case ltRIPPLE_STATE: + lowID = (*after)[sfLowLimit].getIssuer(); + highID = (*after)[sfHighLimit].getIssuer(); + oldBalance = (*before)[sfBalance]; + newBalance = (*after)[sfBalance]; + break; + case ltOFFER: + // TBD + break; + default: + break; + } + } + // The following are now set, put them in the map + auto delta = newBalance - oldBalance; + auto const cur = newBalance.getCurrency(); + result[std::make_tuple(lowID, highID, cur)] = delta; + auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta); + if (r.second) + { + r.first->second += delta; + } + + delta.negate(); + r = result.emplace(std::make_tuple(highID, highID, cur), delta); + if (r.second) + { + r.first->second += delta; + } + }; + items_.visit(view, each); + return result; +} + } // namespace ripple diff --git a/src/ripple/ledger/impl/PaymentSandbox.cpp b/src/ripple/ledger/impl/PaymentSandbox.cpp index bbe2c3134..a3a14f3e4 100644 --- a/src/ripple/ledger/impl/PaymentSandbox.cpp +++ b/src/ripple/ledger/impl/PaymentSandbox.cpp @@ -268,123 +268,7 @@ PaymentSandbox::apply(PaymentSandbox& to) std::map, STAmount> PaymentSandbox::balanceChanges(ReadView const& view) const { - using key_t = std::tuple; - // Map of delta trust lines. As a special case, when both ends of the trust - // line are the same currency, then it's delta currency for that issuer. To - // get the change in XRP balance, Account == root, issuer == root, currency - // == XRP - std::map result; - - // populate a dictionary with low/high/currency/delta. This can be - // compared with the other versions payment code. - auto each = [&result]( - uint256 const& key, - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) { - STAmount oldBalance; - STAmount newBalance; - AccountID lowID; - AccountID highID; - - // before is read from prev view - if (isDelete) - { - if (!before) - return; - - auto const bt = before->getType(); - switch (bt) - { - case ltACCOUNT_ROOT: - lowID = xrpAccount(); - highID = (*before)[sfAccount]; - oldBalance = (*before)[sfBalance]; - newBalance = oldBalance.zeroed(); - break; - case ltRIPPLE_STATE: - lowID = (*before)[sfLowLimit].getIssuer(); - highID = (*before)[sfHighLimit].getIssuer(); - oldBalance = (*before)[sfBalance]; - newBalance = oldBalance.zeroed(); - break; - case ltOFFER: - // TBD - break; - default: - break; - } - } - else if (!before) - { - // insert - auto const at = after->getType(); - switch (at) - { - case ltACCOUNT_ROOT: - lowID = xrpAccount(); - highID = (*after)[sfAccount]; - newBalance = (*after)[sfBalance]; - oldBalance = newBalance.zeroed(); - break; - case ltRIPPLE_STATE: - lowID = (*after)[sfLowLimit].getIssuer(); - highID = (*after)[sfHighLimit].getIssuer(); - newBalance = (*after)[sfBalance]; - oldBalance = newBalance.zeroed(); - break; - case ltOFFER: - // TBD - break; - default: - break; - } - } - else - { - // modify - auto const at = after->getType(); - assert(at == before->getType()); - switch (at) - { - case ltACCOUNT_ROOT: - lowID = xrpAccount(); - highID = (*after)[sfAccount]; - oldBalance = (*before)[sfBalance]; - newBalance = (*after)[sfBalance]; - break; - case ltRIPPLE_STATE: - lowID = (*after)[sfLowLimit].getIssuer(); - highID = (*after)[sfHighLimit].getIssuer(); - oldBalance = (*before)[sfBalance]; - newBalance = (*after)[sfBalance]; - break; - case ltOFFER: - // TBD - break; - default: - break; - } - } - // The following are now set, put them in the map - auto delta = newBalance - oldBalance; - auto const cur = newBalance.getCurrency(); - result[std::make_tuple(lowID, highID, cur)] = delta; - auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta); - if (r.second) - { - r.first->second += delta; - } - - delta.negate(); - r = result.emplace(std::make_tuple(highID, highID, cur), delta); - if (r.second) - { - r.first->second += delta; - } - }; - items_.visit(view, each); - return result; + return ApplyViewBase::balanceChanges(view); } XRPAmount