large refactor for weak/strong tsh, not compiling

This commit is contained in:
Richard Holland
2022-04-21 11:24:59 +00:00
parent 3439888aef
commit 8861467437
9 changed files with 596 additions and 456 deletions

View File

@@ -511,7 +511,7 @@ namespace hook
std::vector<uint8_t>,
std::vector<uint8_t>
>> const& hookParamOverrides,
std::shared_ptr<HookStateMap>& 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>&,
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<ripple::STLedgerEntry> const& hookDef,
ripple::STObject const* hookObj,
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& parameters,
beast::Journal const& j_);
// RH TODO: call destruct for these on rippled shutdown
#define ADD_HOOK_FUNCTION(F, ctx)\
{\

View File

@@ -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;

View File

@@ -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

View File

@@ -33,14 +33,9 @@
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/ledger/PaymentSandbox.h>
#include <limits>
//RH TODO: remove after debugging
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
//--------
#include <set>
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<SLE const> 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<ripple::STLedgerEntry> const& hookDef,
ripple::STObject const* hookObj,
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& 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<STObject const*>(&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<STObject const*>(&hookParameter);
parameters[hookParameterObj->getFieldVL(sfHookParameterName)] =
hookParameterObj->getFieldVL(sfHookParameterValue);
}
}
return false;
}
bool /* retval of true means an error */
TER
Transactor::
executeHookChain(
std::shared_ptr<ripple::STLedgerEntry const> const& hookSLE,
std::shared_ptr<hook::HookStateMap>& stateMap,
std::vector<hook::HookResult>& results,
int& executedHookCount,
ripple::AccountID const& account,
ripple::ApplyContext& ctx,
beast::Journal const& j_,
TER& result)
std::shared_ptr<ripple::STLedgerEntry const> const& hookSLE,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results,
ripple::AccountID const& account)
{
std::set<uint256> 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<uint8_t>, std::vector<uint8_t>> 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<ripple::STTx&>(ctx_.tx).getField(sfEmitDetails).downcast<STObject>();
// 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<STObject const*>(&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<uint8_t>, std::vector<uint8_t>> 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<TxType>(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 << "-" <<ctx_.tx.getAccountID(sfAccount) << "]: "
<< "Callback finalizeHookResult = "
<< result;
}
catch (std::exception& e)
{
JLOG(j_.fatal())
<< "HookError[" << callbackAccountID << "-" <<ctx_.tx.getAccountID(sfAccount) << "]: "
<< "]: Callback failure " << e.what();
}
}
if (!found)
{
JLOG(j_.warn())
<< "HookError[" << callbackAccountID << "]: Hookhash "
<< callbackHookHash << " not found on callback account";
}
}
void
Transactor::
addWeakTSHFromSandbox(ApplyViewBase const& pv)
{
// If Hooks are enabled then non-issuers who have their TL balance
// modified by the execution of the path have the opportunity to have their
// weak hooks executed.
if (ctx_.view().rules().enabled(featureHooks))
{
// anyone whose balance changed as a result of this Pathing is a weak TSH
auto bc = pv.balanceChanges(view());
for (auto const& entry : bc)
{
std::tuple<AccountID, AccountID, Currency> 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<hook::HookResult>& results)
{
auto& view = ctx_.view();
std::vector<std::pair<AccountID, bool>> 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<AccountID> 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<TER, bool>
@@ -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<hook::HookStateMap> stateMap = std::make_shared<hook::HookStateMap>();
// 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<hook::HookResult> sendResults;
std::vector<hook::HookResult> recvResults;
std::vector<hook::HookResult> orgResults;
std::vector<hook::HookResult> 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<std::pair<AccountID, bool>> 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<ripple::STTx&>(ctx_.tx).getField(sfEmitDetails).downcast<STObject>();
// 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<STObject const*>(&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<uint8_t>, std::vector<uint8_t>> 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::HookStateMap>();
hook::HookResult callbackResult =
hook::apply(
hookDef->getFieldH256(sfHookSetTxnID),
callbackHookHash,
ns,
hookDef->getFieldVL(sfCreateCode),
parameters,
{},
stateMap,
ctx_,
callbackAccountID,
true,
safe_cast<TxType>(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 << "-" <<ctx_.tx.getAccountID(sfAccount) << "]: "
<< "Callback finalizeHookResult = "
<< result;
}
catch (std::exception& e)
{
JLOG(j_.fatal())
<< "HookError[" << callbackAccountID << "-" <<ctx_.tx.getAccountID(sfAccount) << "]: "
<< "]: Callback failure " << e.what();
}
break;
}
if (!found)
{
JLOG(j_.warn())
<< "HookError[" << callbackAccountID << "]: Hookhash "
<< callbackHookHash << " not found on callback account";
}
}
}
while(0);
// remove emission entry if this is an emitted transaction
hook::removeEmissionEntry(ctx_);
}
uint64_t posthook_cycles = rdtsc();
JLOG(j_.trace())
<< "HookStats[]: " << executedHookCount << " hooks executed. Execution took "
<< (posthook_cycles - prehook_cycles) << " cycles.";
// fall through allows normal apply
if (result == tesSUCCESS)
result = apply();
@@ -1374,6 +1389,32 @@ Transactor::operator()()
stream << "preclaim result: " << transToken(result);
bool applied = isTesSuccess(result);
// Post-application (Weak TSH) Hooks are executed here.
// These TSH do not have the ability to rollback.
// The callback, if any, is also executed here.
if (hooksEnabled && applied)
{
// perform callback logic if applicable
if (ctx_.tx.isFieldPresent(sfEmitDetails))
doHookCallback();
// remove emission entry if this is an emitted transaction
hook::removeEmissionEntry(ctx_);
// process weak TSH
hook::HookStateMap stateMap;
std::vector<hook::HookResult> 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)

View File

@@ -20,10 +20,13 @@
#ifndef RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED
#define RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED
#include <ripple/app/tx/applyHook.h>
#include <ripple/app/tx/applySteps.h>
#include <ripple/app/tx/impl/ApplyContext.h>
#include <ripple/basics/XRPAmount.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/ledger/PaymentSandbox.h>
#include <ripple/ledger/detail/ApplyViewBase.h>
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<TER, bool>
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<hook::HookResult>& results);
TER
executeHookChain(
std::shared_ptr<ripple::STLedgerEntry const> const& hookSLE,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& 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<AccountID> 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();

View File

@@ -1079,7 +1079,7 @@ DEFINE_HOOK_FUNCTION(
ripple::TER
hook::
finalizeHookState(
std::shared_ptr<HookStateMap>& 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<ripple::STLedgerEntry> const& hookDef,
ripple::STObject const* hookObj,
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& 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<STObject const*>(&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<STObject const*>(&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");

View File

@@ -116,6 +116,14 @@ public:
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> 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<std::tuple<AccountID, AccountID, Currency>, STAmount>
balanceChanges(ReadView const& view) const;
private:
std::optional<STAmount> deliver_;
std::vector<STObject> hookExecution_;

View File

@@ -52,4 +52,126 @@ ApplyViewImpl::visit(
items_.visit(to, func);
}
std::map<std::tuple<AccountID, AccountID, Currency>, STAmount>
ApplyView::balanceChanges(ReadView const& view) const
{
using key_t = std::tuple<AccountID, AccountID, Currency>;
// 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<key_t, STAmount> 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<SLE const> const& before,
std::shared_ptr<SLE const> 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

View File

@@ -268,123 +268,7 @@ PaymentSandbox::apply(PaymentSandbox& to)
std::map<std::tuple<AccountID, AccountID, Currency>, STAmount>
PaymentSandbox::balanceChanges(ReadView const& view) const
{
using key_t = std::tuple<AccountID, AccountID, Currency>;
// 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<key_t, STAmount> 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<SLE const> const& before,
std::shared_ptr<SLE const> 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