add transactional stakeholder code, not compiling

This commit is contained in:
Richard Holland
2022-03-18 13:55:54 +00:00
parent 1bde126fbe
commit 7b834714c2
7 changed files with 325 additions and 33 deletions

View File

@@ -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<uint16_t, uint8_t> 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<std::pair<AccountID, bool>>
getTransactionalStakeHolders(STTx const& tx, ReadView const& rv);
namespace log
{
/*

View File

@@ -152,19 +152,9 @@ Transactor::Transactor(ApplyContext& ctx)
{
}
inline
std::optional<AccountID>
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<AccountID>
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<std::pair<AccountID, bool>> 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<AccountID>
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<std::pair<AccountID, bool>> 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)

View File

@@ -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<std::pair<AccountID, bool>>
getTransactionalStakeHolders(STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx)
{

View File

@@ -19,6 +19,174 @@
using namespace ripple;
namespace hook
{
std::vector<std::pair<AccountID, bool>>
getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
{
if (!rv.rules().enabled(featureHooks))
return {};
if (!tx.isFieldPresent(sfAccount))
return {};
std::optional<AccountID> destAcc = tx.at(sfDestination);
std::optional<AccountID> 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<AccountID, std::pair<int, bool>> 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<int, bool>{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<STObject const*>(&e);
if (entryObj->isFieldPresent(sfAccount))
ADD_TSH(entryObj->getAccountID(sfAccount), canRollback);
}
break;
}
default:
return {};
}
std::vector<std::pair<AccountID, bool>> ret {tshEntries.size()};
for (auto& [a, e] : tshEntries)
ret[e.first] = std::pair<AccountID, bool>{a, e.second};
return std::move(ret);
}
}
namespace hook_float
{

View File

@@ -544,6 +544,25 @@ private:
return jvRequest;
}
// fee [<txblob>]
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 [<min_count>]
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},

View File

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

View File

@@ -25,14 +25,64 @@
#include <ripple/protocol/Feature.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/app/tx/applyHook.h>
#include <ripple/app/tx/impl/Transactor.h>
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<STTx const> stpTrans;
try
{
stpTrans = std::make_unique<STTx const>(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<std::pair<AccountID, bool>> 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;