From 7b834714c2f47c8a143f5adc8a6a44dfa7732ee4 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Fri, 18 Mar 2022 13:55:54 +0000 Subject: [PATCH] add transactional stakeholder code, not compiling --- src/ripple/app/tx/applyHook.h | 40 ++++++ src/ripple/app/tx/impl/Transactor.cpp | 62 +++++----- src/ripple/app/tx/impl/Transactor.h | 11 ++ src/ripple/app/tx/impl/applyHook.cpp | 168 ++++++++++++++++++++++++++ src/ripple/net/impl/RPCCall.cpp | 20 +++ src/ripple/protocol/jss.h | 1 + src/ripple/rpc/handlers/Fee1.cpp | 56 ++++++++- 7 files changed, 325 insertions(+), 33 deletions(-) diff --git a/src/ripple/app/tx/applyHook.h b/src/ripple/app/tx/applyHook.h index 039312582..0aff53799 100644 --- a/src/ripple/app/tx/applyHook.h +++ b/src/ripple/app/tx/applyHook.h @@ -47,6 +47,46 @@ namespace hook bool, // is modified from ledger value ripple::Blob>>>>; // the value + enum TSHFlags : uint8_t + { + tshNONE = 0b000, + tshROLLBACK = 0b001, + tshCOLLECT = 0b010, + }; + + using namespace ripple; + static const std::map TSHAllowances = + { + {ttPAYMENT, tshROLLBACK }, + {ttESCROW_CREATE, tshROLLBACK }, + {ttESCROW_FINISH, tshROLLBACK }, + {ttACCOUNT_SET, tshNONE }, + {ttESCROW_CANCEL, tshCOLLECT }, + {ttREGULAR_KEY_SET, tshNONE }, +// {ttNICKNAME_SET, tshNONE }, + {ttOFFER_CREATE, tshCOLLECT }, + {ttOFFER_CANCEL, tshNONE }, +// {ttCONTRACT, tshNONE }, + {ttTICKET_CREATE, tshNONE }, +// {ttSPINAL_TAP, tshNONE }, + {ttSIGNER_LIST_SET, tshROLLBACK }, + {ttPAYCHAN_CREATE, tshROLLBACK }, + {ttPAYCHAN_FUND, tshCOLLECT }, + {ttPAYCHAN_CLAIM, tshCOLLECT }, + {ttCHECK_CREATE, tshROLLBACK }, + {ttCHECK_CASH, tshROLLBACK }, + {ttCHECK_CANCEL, tshCOLLECT }, + {ttDEPOSIT_PREAUTH, tshROLLBACK }, + {ttTRUST_SET, tshCOLLECT }, + {ttACCOUNT_DELETE, tshROLLBACK }, + {ttHOOK_SET, tshNONE } + // RH TODO: add NFT transactions here + }; + + + std::vector> + getTransactionalStakeHolders(STTx const& tx, ReadView const& rv); + namespace log { /* diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index f8988d9a8..106b321e5 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -152,19 +152,9 @@ Transactor::Transactor(ApplyContext& ctx) { } -inline -std::optional -getDestinationAccount(STTx const& tx) -{ - if (tx.isFieldPresent(sfOwner)) - return tx.getAccountID(sfOwner); - - if (tx.isFieldPresent(sfDestination)) - return tx.getAccountID(sfDestination); - - return std::nullopt; -} +// 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) { @@ -210,7 +200,6 @@ Transactor::calculateHookChainFee(ReadView const& view, STTx const& tx, Keylet c } return fee; - } FeeUnit64 @@ -255,13 +244,14 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) hookExecutionFee += calculateHookChainFee(view, tx, keylet::hook(tx.getAccountID(sfAccount))); - std::optional - destAccountID = getDestinationAccount(tx); - - // if there is a receiving account then we also compute the fee for its hook chain - if (destAccountID) - hookExecutionFee += - calculateHookChainFee(view, tx, keylet::hook(*destAccountID)); + // find any additional stakeholders whose hooks will be executed and charged to this transaction + std::vector> tsh = + hook::getTransactionalStakeHolders(tx, view); + + for (auto& [tshAcc, canRollback] : tsh) + if (canRollback) + hookExecutionFee += + calculateHookChainFee(view, tx, keylet::hook(tshAcc)); } return baseFee + (signerCount * baseFee) + hookExecutionFee; // RH NOTE: hookExecutionFee = 0 @@ -1118,17 +1108,29 @@ Transactor::operator()() rollback = executeHookChain(hooksSending, stateMap, sendResults, executedHookCount, accountID, ctx_, j_, result); - // Next check if the Receiving account has as a hook that can be fired... - std::optional - destAccountID = getDestinationAccount(ctx_.tx); - - if (!rollback && destAccountID) + // Next check if there are any transactional stake holders whose hooks need to be executed + if (!rollback) { - auto const& hooksReceiving = ledger.read(keylet::hook(*destAccountID)); - if (hooksReceiving && hooksReceiving->isFieldPresent(sfHooks)) - rollback = - executeHookChain(hooksReceiving, stateMap, - recvResults, executedHookCount, *destAccountID, ctx_, j_, result); + std::vector> tsh = + hook::getTransactionalStakeHolders(ctx_.tx, ledger); + + for (auto& [tshAccountID, canRollback] : tsh) + { + auto const& hooksReceiving = ledger.read(keylet::hook(tshAccountID)); + if (hooksReceiving && hooksReceiving->isFieldPresent(sfHooks)) + { + bool tshRollback = + executeHookChain(hooksReceiving, stateMap, + recvResults, executedHookCount, tshAccountID, ctx_, j_, result); + if (canRollback && tshRollback) + { + rollback = true; + break; + } + + //RH TODO: charge non-rollback tsh executions a fee here + } + } } if (rollback && result != temMALFORMED) diff --git a/src/ripple/app/tx/impl/Transactor.h b/src/ripple/app/tx/impl/Transactor.h index 5449ac0ea..5c6349ee5 100644 --- a/src/ripple/app/tx/impl/Transactor.h +++ b/src/ripple/app/tx/impl/Transactor.h @@ -154,6 +154,17 @@ public: static FeeUnit64 calculateHookChainFee(ReadView const& view, STTx const& tx, Keylet const& hookKeylet); + + // Returns a list of zero or more accounts which are + // not the originator of the transaction but which are + // stakeholders in the transaction. The bool parameter + // determines whether or not the specified account has + // permission for their hook/s to cause a rollback on + // the transaction. + static std::vector> + getTransactionalStakeHolders(STTx const& tx); + + static TER preclaim(PreclaimContext const& ctx) { diff --git a/src/ripple/app/tx/impl/applyHook.cpp b/src/ripple/app/tx/impl/applyHook.cpp index 792d6832c..a99af3d23 100644 --- a/src/ripple/app/tx/impl/applyHook.cpp +++ b/src/ripple/app/tx/impl/applyHook.cpp @@ -19,6 +19,174 @@ using namespace ripple; +namespace hook +{ + + std::vector> + getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) + { + + if (!rv.rules().enabled(featureHooks)) + return {}; + + if (!tx.isFieldPresent(sfAccount)) + return {}; + + std::optional destAcc = tx.at(sfDestination); + std::optional otxnAcc = tx.at(sfAccount); + + if (!otxnAcc) + return {}; + + uint16_t tt = tx.getFieldU16(sfTransactionType); + + uint8_t tsh = tshNONE; + if (auto const& found = hook::TSHAllowances.find(tt); found != hook::TSHAllowances.end()) + tsh = found->second; + else + return {}; + + std::map> tshEntries; + + int upto = 0; + + bool canRollback = tsh & tshROLLBACK; + + #define ADD_TSH(acc, rb)\ + {\ + auto acc_r = acc;\ + if (acc_r != *otxnAcc)\ + {\ + if (tshEntries.find(acc_r) != tshEntries.end())\ + {\ + if (tshEntries[acc_r].second == false && rb)\ + tshEntries[acc_r].second = true;\ + }\ + else\ + tshEntries.emplace(acc_r, std::pair{upto++, rb});\ + }\ + } + + switch (tt) + { + + // self transactions + case ttACCOUNT_SET: + case ttOFFER_CANCEL: + case ttREGULAR_KEY_SET: + case ttTICKET_CREATE: + case ttHOOK_SET: + case ttOFFER_CREATE: // this is handled seperately + //case ttCONTRACT: // not used + //case ttSPINAL_TAP: // not used + //case ttNICKNAME_SET: // not used + { + break; + } + + case ttDEPOSIT_PREAUTH: + { + if (!tx.isFieldPresent(sfAuthorize)) + return {}; + ADD_TSH(tx.getAccountID(sfAuthorize), canRollback); + break; + } + + // simple two party transactions + case ttPAYMENT: + case ttESCROW_CREATE: + case ttCHECK_CREATE: + case ttACCOUNT_DELETE: + case ttPAYCHAN_CREATE: + { + ADD_TSH(*destAcc, canRollback); + break; + } + + case ttTRUST_SET: + { + if (!tx.isFieldPresent(sfLimitAmount)) + return {}; + + auto const& lim = tx.getFieldAmount(sfLimitAmount); + AccountID const& issuer = lim.getIssuer(); + + ADD_TSH(issuer, canRollback); + break; + } + + case ttESCROW_CANCEL: + case ttESCROW_FINISH: + { + if (!tx.isFieldPresent(sfOwner) || !tx.isFieldPresent(sfOfferSequence)) + return {}; + + auto escrow = rv.read( + keylet::escrow(tx.getAccountID(sfOwner), tx.getFieldU32(sfOfferSequence))); + + if (!escrow) + return {}; + + ADD_TSH(escrow->getAccountID(sfAccount), true); + ADD_TSH(escrow->getAccountID(sfDestination), canRollback); + break; + } + + case ttPAYCHAN_FUND: + case ttPAYCHAN_CLAIM: + { + if (!tx.isFieldPresent(sfChannel)) + return {}; + + auto chan = rv.read(Keylet {ltPAYCHAN, tx.getFieldH256(sfChannel)}); + if (!chan) + return {}; + + ADD_TSH(chan->getAccountID(sfAccount), true); + ADD_TSH(chan->getAccountID(sfDestination), canRollback); + break; + } + + case ttCHECK_CASH: + case ttCHECK_CANCEL: + { + if (!tx.isFieldPresent(sfCheckID)) + return {}; + + auto check = rv.read(Keylet {ltCHECK, tx.getFieldH256(sfCheckID)}); + if (!check) + return {}; + + ADD_TSH(check->getAccountID(sfAccount), true); + ADD_TSH(check->getAccountID(sfDestination), canRollback); + break; + } + + // the owners of accounts whose keys appear on a signer list are entitled to prevent their inclusion + case ttSIGNER_LIST_SET: + { + STArray const& signerEntries = tx.getFieldArray(sfSignerEntries); + for (auto const& e : signerEntries) + { + auto const& entryObj = dynamic_cast(&e); + if (entryObj->isFieldPresent(sfAccount)) + ADD_TSH(entryObj->getAccountID(sfAccount), canRollback); + } + break; + } + + default: + return {}; + } + + std::vector> ret {tshEntries.size()}; + for (auto& [a, e] : tshEntries) + ret[e.first] = std::pair{a, e.second}; + + return std::move(ret); + } + +} namespace hook_float { diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index a55422a4c..6c97b5c6a 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -544,6 +544,25 @@ private: return jvRequest; } + // fee [] + Json::Value + parseFee(Json::Value const& jvParams) + { + Json::Value jvRequest(Json::objectValue); + + if (jvParams.size() == 0) + { + return jvRequest; + } + else if (jvParams.size() > 1) + { + return rpcError(rpcINVALID_PARAMS); + } + + jvRequest[jss::tx_blob] = jvParams[0u].asString(); + return jvRequest; + } + // get_counts [] Json::Value parseGetCounts(Json::Value const& jvParams) @@ -1313,6 +1332,7 @@ public: {"deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 3}, {"download_shard", &RPCParser::parseDownloadShard, 2, -1}, {"feature", &RPCParser::parseFeature, 0, 2}, + {"fee", &RPCParser::parseFee, 0, 1}, {"fetch_info", &RPCParser::parseFetchInfo, 0, 1}, {"gateway_balances", &RPCParser::parseGatewayBalances, 1, -1}, {"get_counts", &RPCParser::parseGetCounts, 0, 1}, diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 05152ac6a..947d3cd98 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -253,6 +253,7 @@ JSS(features); // out: Feature JSS(fee); // out: NetworkOPs, Peers JSS(fee_base); // out: NetworkOPs JSS(fee_div_max); // in: TransactionSign +JSS(fee_hooks_feeunits); // out: Fee rpc call JSS(fee_level); // out: AccountInfo JSS(fee_mult_max); // in: TransactionSign JSS(fee_ref); // out: NetworkOPs diff --git a/src/ripple/rpc/handlers/Fee1.cpp b/src/ripple/rpc/handlers/Fee1.cpp index f28f9b447..e30f3cbb2 100644 --- a/src/ripple/rpc/handlers/Fee1.cpp +++ b/src/ripple/rpc/handlers/Fee1.cpp @@ -25,14 +25,64 @@ #include #include #include +#include +#include namespace ripple { Json::Value doFee(RPC::JsonContext& context) { - auto result = context.app.getTxQ().doRPC(context.app); - if (result.type() == Json::objectValue) - return result; + Json::Value jvResult = context.app.getTxQ().doRPC(context.app); + if (jvResult.type() == Json::objectValue) + { + auto const& params(context.params); + if (params.isMember(jss::tx_blob)) + { + auto ret = strUnHex(context.params[jss::tx_blob].asString()); + + if (!ret || !ret->size()) + return rpcError(rpcINVALID_PARAMS); + + SerialIter sitTrans(makeSlice(*ret)); + + std::unique_ptr stpTrans; + try + { + stpTrans = std::make_unique(std::ref(sitTrans)); + } + catch (std::exception& e) + { + jvResult[jss::error] = "invalidTransaction"; + jvResult[jss::error_exception] = e.what(); + return jvResult; + } + + if (!stpTrans->isFieldPresent(sfAccount)) + { + jvResult[jss::error] = "invalidTransaction"; + jvResult[jss::error_exception] = "No sfAccount specified"; + return jvResult; + } + + FeeUnit64 hookFees = + Transactor::calculateHookChainFee(*(context.app.openLedger().current()), + *stpTrans, keylet::hook(stpTrans->getAccountID(sfAccount))); + + auto const view = context.app.openLedger().current(); + + std::vector> tsh = + hook::getTransactionalStakeHolders(*stpTrans, *view); + + for (auto const& [tshAccount, canRollback] : tsh) + if (canRollback) + hookFees += + Transactor::calculateHookChainFee(*view, *stpTrans, keylet::hook(tshAccount)); + + jvResult[jss::fee_hooks_feeunits] = to_string(hookFees.value()); + } + + return jvResult; + } assert(false); RPC::inject_error(rpcINTERNAL, context.params); return context.params;