diff --git a/CMakeLists.txt b/CMakeLists.txt index d5aa0979d4..5d234121fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1998,6 +1998,7 @@ else () src/ripple/rpc/handlers/ValidatorListSites.cpp src/ripple/rpc/handlers/Validators.cpp src/ripple/rpc/handlers/WalletPropose.cpp + src/ripple/rpc/impl/DeliveredAmount.cpp src/ripple/rpc/impl/Handler.cpp src/ripple/rpc/impl/LegacyPathFind.cpp src/ripple/rpc/impl/RPCHandler.cpp @@ -2268,6 +2269,7 @@ else () src/test/rpc/AmendmentBlocked_test.cpp src/test/rpc/Book_test.cpp src/test/rpc/DepositAuthorized_test.cpp + src/test/rpc/DeliveredAmount_test.cpp src/test/rpc/Feature_test.cpp src/test/rpc/GatewayBalances_test.cpp src/test/rpc/GetCounts_test.cpp diff --git a/src/ripple/app/ledger/AcceptedLedgerTx.cpp b/src/ripple/app/ledger/AcceptedLedgerTx.cpp index c5533800b2..4c6099ce81 100644 --- a/src/ripple/app/ledger/AcceptedLedgerTx.cpp +++ b/src/ripple/app/ledger/AcceptedLedgerTx.cpp @@ -35,8 +35,8 @@ AcceptedLedgerTx::AcceptedLedgerTx ( : mLedger (ledger) , mTxn (txn) , mMeta (std::make_shared ( - txn->getTransactionID(), ledger->seq(), *met, logs.journal ("View"))) - , mAffected (mMeta->getAffectedAccounts ()) + txn->getTransactionID(), ledger->seq(), *met)) + , mAffected (mMeta->getAffectedAccounts (logs.journal("View"))) , accountCache_ (accountCache) , logs_ (logs) { diff --git a/src/ripple/app/ledger/LedgerHistory.cpp b/src/ripple/app/ledger/LedgerHistory.cpp index 7110b5e30d..a470a01536 100644 --- a/src/ripple/app/ledger/LedgerHistory.cpp +++ b/src/ripple/app/ledger/LedgerHistory.cpp @@ -170,13 +170,13 @@ log_metadata_difference( uint256 const& tx, beast::Journal j) { - auto getMeta = [j](ReadView const& ledger, + auto getMeta = [](ReadView const& ledger, uint256 const& txID) -> std::shared_ptr { auto meta = ledger.txRead(txID).second; if (!meta) return {}; - return std::make_shared (txID, ledger.seq(), *meta, j); + return std::make_shared (txID, ledger.seq(), *meta); }; auto validMetaData = getMeta (validLedger, tx); diff --git a/src/ripple/app/ledger/impl/LedgerToJson.cpp b/src/ripple/app/ledger/impl/LedgerToJson.cpp index 89b0104542..13404272a5 100644 --- a/src/ripple/app/ledger/impl/LedgerToJson.cpp +++ b/src/ripple/app/ledger/impl/LedgerToJson.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace ripple { @@ -97,49 +99,63 @@ void fillJsonBinary(Object& json, bool closed, LedgerInfo const& info) } } -Json::Value fillJsonTx (LedgerFill const& fill, - bool bBinary, bool bExpanded, - std::pair, - std::shared_ptr> const i) +Json::Value +fillJsonTx( + LedgerFill const& fill, + bool bBinary, + bool bExpanded, + std::shared_ptr const& txn, + std::shared_ptr const& stMeta) { - if (! bExpanded) + if (!bExpanded) + return to_string(txn->getTransactionID()); + + Json::Value txJson{Json::objectValue}; + auto const txnType = txn->getTxnType(); + if (bBinary) { - return to_string(i.first->getTransactionID()); + txJson[jss::tx_blob] = serializeHex(*txn); + if (stMeta) + txJson[jss::meta] = serializeHex(*stMeta); } else { - Json::Value txJson{ Json::objectValue }; - if (bBinary) + copyFrom(txJson, txn->getJson(0)); + if (stMeta) { - txJson[jss::tx_blob] = serializeHex(*i.first); - if (i.second) - txJson[jss::meta] = serializeHex(*i.second); - } - else - { - copyFrom(txJson, i.first->getJson(0)); - if (i.second) - txJson[jss::metaData] = i.second->getJson(0); - } - - if ((fill.options & LedgerFill::ownerFunds) && - i.first->getTxnType() == ttOFFER_CREATE) - { - auto const account = i.first->getAccountID(sfAccount); - auto const amount = i.first->getFieldAmount(sfTakerGets); - - // If the offer create is not self funded then add the - // owner balance - if (account != amount.getIssuer()) + txJson[jss::metaData] = stMeta->getJson(0); + if (txnType == ttPAYMENT || txnType == ttCHECK_CASH) { - auto const ownerFunds = accountFunds(fill.ledger, - account, amount, fhIGNORE_FREEZE, - beast::Journal {beast::Journal::getNullSink()}); - txJson[jss::owner_funds] = ownerFunds.getText (); + // Insert delivered amount + auto txMeta = std::make_shared( + txn->getTransactionID(), fill.ledger.seq(), *stMeta); + RPC::insertDeliveredAmount( + txJson[jss::metaData], fill.ledger, txn, *txMeta); } } - return txJson; } + + if ((fill.options & LedgerFill::ownerFunds) && + txn->getTxnType() == ttOFFER_CREATE) + { + auto const account = txn->getAccountID(sfAccount); + auto const amount = txn->getFieldAmount(sfTakerGets); + + // If the offer create is not self funded then add the + // owner balance + if (account != amount.getIssuer()) + { + auto const ownerFunds = accountFunds( + fill.ledger, + account, + amount, + fhIGNORE_FREEZE, + beast::Journal{beast::Journal::getNullSink()}); + txJson[jss::owner_funds] = ownerFunds.getText(); + } + } + + return txJson; } template @@ -153,7 +169,7 @@ void fillJsonTx (Object& json, LedgerFill const& fill) { for (auto& i: fill.ledger.txs) { - txns.append(fillJsonTx(fill, bBinary, bExpanded, i)); + txns.append(fillJsonTx(fill, bBinary, bExpanded, i.first, i.second)); } } catch (std::exception const&) @@ -219,8 +235,7 @@ void fillJsonQueue(Object& json, LedgerFill const& fill) if (tx.lastResult) txJson["last_result"] = transToken(*tx.lastResult); - txJson[jss::tx] = fillJsonTx(fill, bBinary, bExpanded, - std::make_pair(tx.txn, nullptr)); + txJson[jss::tx] = fillJsonTx(fill, bBinary, bExpanded, tx.txn, nullptr); } } diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 8a33e49c65..77ae7dcc95 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -2063,7 +2063,7 @@ void ApplicationImp::addTxnSeqField () } else { - TxMeta m (transID, 0, txnMeta, journal ("TxMeta")); + TxMeta m (transID, 0, txnMeta); txIDs.push_back (std::make_pair (transID, m.getIndex ())); } diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index b8bec3776a..224a51a548 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -1963,9 +1964,10 @@ NetworkOPs::AccountTxs NetworkOPsImp::getAccountTxs ( } if (txn) - ret.emplace_back (txn, std::make_shared ( - txn->getID (), txn->getLedger (), txnMeta, - app_.journal("TxMeta"))); + ret.emplace_back( + txn, + std::make_shared( + txn->getID(), txn->getLedger(), txnMeta)); } } @@ -2556,9 +2558,16 @@ void NetworkOPsImp::pubValidatedTransaction ( std::shared_ptr const& alAccepted, const AcceptedLedgerTx& alTx) { + std::shared_ptr stTxn = alTx.getTxn(); Json::Value jvObj = transJson ( - *alTx.getTxn (), alTx.getResult (), true, alAccepted); - jvObj[jss::meta] = alTx.getMeta ()->getJson (0); + *stTxn, alTx.getResult (), true, alAccepted); + + if (auto const txMeta = alTx.getMeta()) + { + jvObj[jss::meta] = txMeta->getJson(0); + RPC::insertDeliveredAmount( + jvObj[jss::meta], *alAccepted, stTxn, *txMeta); + } { ScopedLockType sl (mSubLock); @@ -2666,11 +2675,19 @@ void NetworkOPsImp::pubAccountTransaction ( if (!notify.empty ()) { + std::shared_ptr stTxn = alTx.getTxn(); Json::Value jvObj = transJson ( - *alTx.getTxn (), alTx.getResult (), bAccepted, lpCurrent); + *stTxn, alTx.getResult (), bAccepted, lpCurrent); if (alTx.isApplied ()) - jvObj[jss::meta] = alTx.getMeta ()->getJson (0); + { + if (auto const txMeta = alTx.getMeta()) + { + jvObj[jss::meta] = txMeta->getJson(0); + RPC::insertDeliveredAmount( + jvObj[jss::meta], *lpCurrent, stTxn, *txMeta); + } + } for (InfoSub::ref isrListener : notify) isrListener->send (jvObj, true); diff --git a/src/ripple/app/misc/impl/AccountTxPaging.cpp b/src/ripple/app/misc/impl/AccountTxPaging.cpp index 7820dd2b6e..b62c85b599 100644 --- a/src/ripple/app/misc/impl/AccountTxPaging.cpp +++ b/src/ripple/app/misc/impl/AccountTxPaging.cpp @@ -48,7 +48,7 @@ convertBlobsToTxResult ( tr->setLedger (ledger_index); auto metaset = std::make_shared ( - tr->getID (), tr->getLedger (), rawMeta, app.journal ("TxMeta")); + tr->getID (), tr->getLedger (), rawMeta); to.emplace_back(std::move(tr), metaset); }; diff --git a/src/ripple/ledger/TxMeta.h b/src/ripple/ledger/TxMeta.h index 6566e5ec9c..9c2abfbd1d 100644 --- a/src/ripple/ledger/TxMeta.h +++ b/src/ripple/ledger/TxMeta.h @@ -42,29 +42,26 @@ private: explicit CtorHelper() = default; }; template - TxMeta (uint256 const& txID, std::uint32_t ledger, T const& data, beast::Journal j, - CtorHelper); + TxMeta (uint256 const& txID, std::uint32_t ledger, T const& data, CtorHelper); public: - TxMeta (beast::Journal j) + TxMeta () : mLedger (0) , mIndex (static_cast (-1)) , mResult (255) - , j_ (j) { } - TxMeta (uint256 const& txID, std::uint32_t ledger, std::uint32_t index, beast::Journal j) + TxMeta (uint256 const& txID, std::uint32_t ledger, std::uint32_t index) : mTransactionID (txID) , mLedger (ledger) , mIndex (static_cast (-1)) , mResult (255) - , j_(j) { } - TxMeta (uint256 const& txID, std::uint32_t ledger, Blob const&, beast::Journal j); - TxMeta (uint256 const& txID, std::uint32_t ledger, std::string const&, beast::Journal j); - TxMeta (uint256 const& txID, std::uint32_t ledger, STObject const&, beast::Journal j); + TxMeta (uint256 const& txID, std::uint32_t ledger, Blob const&); + TxMeta (uint256 const& txID, std::uint32_t ledger, std::string const&); + TxMeta (uint256 const& txID, std::uint32_t ledger, STObject const&); void init (uint256 const& transactionID, std::uint32_t ledger); void clear () @@ -103,7 +100,7 @@ public: /** Return a list of accounts affected by this transaction */ boost::container::flat_set - getAffectedAccounts() const; + getAffectedAccounts(beast::Journal j) const; Json::Value getJson (int p) const { @@ -144,8 +141,6 @@ private: boost::optional mDelivered; STArray mNodes; - - beast::Journal j_; }; } // ripple diff --git a/src/ripple/ledger/impl/ApplyStateTable.cpp b/src/ripple/ledger/impl/ApplyStateTable.cpp index ec1e217524..90c68c95ac 100644 --- a/src/ripple/ledger/impl/ApplyStateTable.cpp +++ b/src/ripple/ledger/impl/ApplyStateTable.cpp @@ -116,7 +116,7 @@ ApplyStateTable::apply (OpenView& to, std::shared_ptr sMeta; if (!to.open()) { - TxMeta meta(j); + TxMeta meta; // VFALCO Shouldn't TxMeta ctor do this? meta.init (tx.getTransactionID(), to.seq()); if (deliver) diff --git a/src/ripple/ledger/impl/TxMeta.cpp b/src/ripple/ledger/impl/TxMeta.cpp index 76f10cbee1..3ed98ba96f 100644 --- a/src/ripple/ledger/impl/TxMeta.cpp +++ b/src/ripple/ledger/impl/TxMeta.cpp @@ -30,11 +30,10 @@ namespace ripple { template TxMeta::TxMeta (uint256 const& txid, - std::uint32_t ledger, T const& data, beast::Journal j, CtorHelper) + std::uint32_t ledger, T const& data, CtorHelper) : mTransactionID (txid) , mLedger (ledger) , mNodes (sfAffectedNodes, 32) - , j_ (j) { SerialIter sit (makeSlice(data)); @@ -47,12 +46,10 @@ TxMeta::TxMeta (uint256 const& txid, setDeliveredAmount (obj.getFieldAmount (sfDeliveredAmount)); } -TxMeta::TxMeta (uint256 const& txid, std::uint32_t ledger, STObject const& obj, - beast::Journal j) +TxMeta::TxMeta (uint256 const& txid, std::uint32_t ledger, STObject const& obj) : mTransactionID (txid) , mLedger (ledger) , mNodes (obj.getFieldArray (sfAffectedNodes)) - , j_ (j) { mResult = obj.getFieldU8 (sfTransactionResult); mIndex = obj.getFieldU32 (sfTransactionIndex); @@ -69,17 +66,15 @@ TxMeta::TxMeta (uint256 const& txid, std::uint32_t ledger, STObject const& obj, TxMeta::TxMeta (uint256 const& txid, std::uint32_t ledger, - Blob const& vec, - beast::Journal j) - : TxMeta (txid, ledger, vec, j, CtorHelper ()) + Blob const& vec) + : TxMeta (txid, ledger, vec, CtorHelper ()) { } TxMeta::TxMeta (uint256 const& txid, std::uint32_t ledger, - std::string const& data, - beast::Journal j) - : TxMeta (txid, ledger, data, j, CtorHelper ()) + std::string const& data) + : TxMeta (txid, ledger, data, CtorHelper ()) { } @@ -117,7 +112,7 @@ void TxMeta::setAffectedNode (uint256 const& node, SField const& type, } boost::container::flat_set -TxMeta::getAffectedAccounts() const +TxMeta::getAffectedAccounts(beast::Journal j) const { boost::container::flat_set list; list.reserve (10); @@ -156,7 +151,7 @@ TxMeta::getAffectedAccounts() const } else { - JLOG (j_.fatal()) << "limit is not amount " << field.getJson (0); + JLOG (j.fatal()) << "limit is not amount " << field.getJson (0); } } } diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index bf49b5aae0..c7aa2e95e3 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -140,7 +140,7 @@ JSS ( dbKBLedger ); // out: getCounts JSS ( dbKBTotal ); // out: getCounts JSS ( dbKBTransaction ); // out: getCounts JSS ( debug_signing ); // in: TransactionSign -JSS ( delivered_amount ); // out: addPaymentDeliveredAmount +JSS ( delivered_amount ); // out: insertDeliveredAmount JSS ( deposit_authorized ); // out: deposit_authorized JSS ( deposit_preauth ); // in: AccountObjects, LedgerData JSS ( deprecated ); // out diff --git a/src/ripple/rpc/DeliveredAmount.h b/src/ripple/rpc/DeliveredAmount.h new file mode 100644 index 0000000000..b6925b8817 --- /dev/null +++ b/src/ripple/rpc/DeliveredAmount.h @@ -0,0 +1,68 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_RPC_DELIVEREDAMOUNT_H_INCLUDED +#define RIPPLE_RPC_DELIVEREDAMOUNT_H_INCLUDED + +#include + +namespace Json { +class Value; +} + +namespace ripple { + +class ReadView; +class Transaction; +class TxMeta; +class STTx; + +namespace RPC { + +struct Context; + +/** + Add a `delivered_amount` field to the `meta` input/output parameter. + The field is only added to successful payment and check cash transactions. + If a delivered amount field is available in the TxMeta parameter, that value + is used. Otherwise, the transaction's `Amount` field is used. If neither is available, + then the delivered amount is set to "unavailable". + + @{ + */ +void +insertDeliveredAmount( + Json::Value& meta, + ReadView const&, + std::shared_ptr serializedTx, + TxMeta const&); + +void +insertDeliveredAmount( + Json::Value& meta, + Context&, + std::shared_ptr, + TxMeta const&); + +/** @} */ + +} // RPC +} // ripple + +#endif diff --git a/src/ripple/rpc/handlers/AccountTx.cpp b/src/ripple/rpc/handlers/AccountTx.cpp index 44ef17f838..6fe44427fd 100644 --- a/src/ripple/rpc/handlers/AccountTx.cpp +++ b/src/ripple/rpc/handlers/AccountTx.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -166,7 +167,7 @@ Json::Value doAccountTx (RPC::Context& context) if (it.second) { auto meta = it.second->getJson (1); - addPaymentDeliveredAmount (meta, context, it.first, it.second); + insertDeliveredAmount (meta, context, it.first, *it.second); jvObj[jss::meta] = std::move(meta); std::uint32_t uLedgerIndex = it.second->getLgrSeq (); diff --git a/src/ripple/rpc/handlers/AccountTxOld.cpp b/src/ripple/rpc/handlers/AccountTxOld.cpp index be3645f4bc..dc843cd993 100644 --- a/src/ripple/rpc/handlers/AccountTxOld.cpp +++ b/src/ripple/rpc/handlers/AccountTxOld.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -183,7 +184,7 @@ Json::Value doAccountTxOld (RPC::Context& context) std::uint32_t uLedgerIndex = it->second->getLgrSeq (); auto meta = it->second->getJson(0); - addPaymentDeliveredAmount(meta, context, it->first, it->second); + insertDeliveredAmount(meta, context, it->first, *it->second); jvObj[jss::meta] = std::move(meta); jvObj[jss::validated] diff --git a/src/ripple/rpc/handlers/Tx.cpp b/src/ripple/rpc/handlers/Tx.cpp index 47c54a8df5..c1320167c5 100644 --- a/src/ripple/rpc/handlers/Tx.cpp +++ b/src/ripple/rpc/handlers/Tx.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace ripple { @@ -126,12 +127,12 @@ Json::Value doTx (RPC::Context& context) auto rawMeta = lgr->txRead (txn->getID()).second; if (rawMeta) { - auto txMeta = std::make_shared (txn->getID (), - lgr->seq (), *rawMeta, context.app.journal ("TxMeta")); + auto txMeta = std::make_shared( + txn->getID(), lgr->seq(), *rawMeta); okay = true; auto meta = txMeta->getJson (0); - addPaymentDeliveredAmount (meta, context, txn, txMeta); - ret[jss::meta] = meta; + insertDeliveredAmount (meta, context, txn, *txMeta); + ret[jss::meta] = std::move(meta); } } diff --git a/src/ripple/rpc/impl/DeliveredAmount.cpp b/src/ripple/rpc/impl/DeliveredAmount.cpp new file mode 100644 index 0000000000..4d1c925a9e --- /dev/null +++ b/src/ripple/rpc/impl/DeliveredAmount.cpp @@ -0,0 +1,173 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace RPC { + +/* + GetLedgerIndex and GetCloseTime are lambdas that allow the close time and + ledger index to be lazily calculated. Without these lambdas, these values + would be calculated even when not needed, and in some circumstances they are + not trivial to compute. + + GetFix1623Enabled is a callable that returns a bool + GetLedgerIndex is a callable that returns a LedgerIndex + GetCloseTime is a callable that returns a + boost::optional + */ +template +void +insertDeliveredAmount( + Json::Value& meta, + GetFix1623Enabled const& getFix1623Enabled, + GetLedgerIndex const& getLedgerIndex, + GetCloseTime const& getCloseTime, + std::shared_ptr serializedTx, + TxMeta const& transactionMeta) +{ + { + TxType const tt{serializedTx->getTxnType()}; + if (tt != ttPAYMENT && + tt != ttCHECK_CASH) + return; + + if (tt == ttCHECK_CASH && + !getFix1623Enabled()) + return; + } + + // if the transaction failed nothing could have been delivered. + if (transactionMeta.getResultTER() != tesSUCCESS) + return; + + if (transactionMeta.hasDeliveredAmount()) + { + meta[jss::delivered_amount] = + transactionMeta.getDeliveredAmount().getJson(1); + return; + } + + if (serializedTx->isFieldPresent(sfAmount)) + { + using namespace std::chrono_literals; + + // Ledger 4594095 is the first ledger in which the DeliveredAmount field + // was present when a partial payment was made and its absence indicates + // that the amount delivered is listed in the Amount field. + // + // If the ledger closed long after the DeliveredAmount code was deployed + // then its absence indicates that the amount delivered is listed in the + // Amount field. DeliveredAmount went live January 24, 2014. + // 446000000 is in Feb 2014, well after DeliveredAmount went live + if (getLedgerIndex() >= 4594095 || + getCloseTime() > NetClock::time_point{446000000s}) + { + meta[jss::delivered_amount] = + serializedTx->getFieldAmount(sfAmount).getJson(1); + return; + } + } + + // report "unavailable" which cannot be parsed into a sensible amount. + meta[jss::delivered_amount] = Json::Value("unavailable"); +} + +void +insertDeliveredAmount( + Json::Value& meta, + ReadView const& ledger, + std::shared_ptr serializedTx, + TxMeta const& transactionMeta) +{ + if (!serializedTx) + return; + + auto const info = ledger.info(); + auto const getFix1623Enabled = [&ledger] { + return ledger.rules().enabled(fix1623); + }; + auto const getLedgerIndex = [&info] { + return info.seq; + }; + auto const getCloseTime = [&info] { + return info.closeTime; + }; + + insertDeliveredAmount( + meta, + getFix1623Enabled, + getLedgerIndex, + getCloseTime, + std::move(serializedTx), + transactionMeta); +} + +void +insertDeliveredAmount( + Json::Value& meta, + RPC::Context& context, + std::shared_ptr transaction, + TxMeta const& transactionMeta) +{ + if (!transaction) + return; + + auto const serializedTx = transaction->getSTransaction (); + if (! serializedTx) + return; + + + // These lambdas are used to compute the values lazily + auto const getFix1623Enabled = [&context]() -> bool { + auto const view = context.app.openLedger().current(); + if (!view) + return false; + return view->rules().enabled(fix1623); + }; + auto const getLedgerIndex = [&transaction]() -> LedgerIndex { + return transaction->getLedger(); + }; + auto const getCloseTime = + [&context, &transaction]() -> boost::optional { + return context.ledgerMaster.getCloseTimeBySeq(transaction->getLedger()); + }; + + insertDeliveredAmount( + meta, + getFix1623Enabled, + getLedgerIndex, + getCloseTime, + std::move(serializedTx), + transactionMeta); +} +} // RPC +} // ripple diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index 00a83cedec..3162ffc7d1 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -393,86 +394,6 @@ parseAccountIds(Json::Value const& jvArray) return result; } -void -addPaymentDeliveredAmount(Json::Value& meta, RPC::Context& context, - std::shared_ptr transaction, TxMeta::pointer transactionMeta) -{ - // We only want to add a "delivered_amount" field if the transaction - // succeeded - otherwise nothing could have been delivered. - if (! transaction) - return; - - auto const serializedTx = transaction->getSTransaction (); - if (! serializedTx) - return; - - { - // Only include this field for Payment and CheckCash transactions. - TxType const tt {serializedTx->getTxnType()}; - if ((tt != ttPAYMENT) && (tt != ttCHECK_CASH)) - return; - - // Only include this field for CheckCash transactions if the fix - // is enabled. - if (tt == ttCHECK_CASH) - { - auto const view = context.app.openLedger().current(); - if (!view || !view->rules().enabled (fix1623)) - return; - } - } - - if (transactionMeta) - { - if (transactionMeta->getResultTER() != tesSUCCESS) - return; - - // If the transaction explicitly specifies a DeliveredAmount in the - // metadata then we use it. - if (transactionMeta->hasDeliveredAmount ()) - { - meta[jss::delivered_amount] = - transactionMeta->getDeliveredAmount ().getJson (1); - return; - } - } - else if (transaction->getResult() != tesSUCCESS) - { - return; - } - - if (serializedTx->isFieldPresent (sfAmount)) - { - // Ledger 4594095 is the first ledger in which the DeliveredAmount field - // was present when a partial payment was made and its absence indicates - // that the amount delivered is listed in the Amount field. - if (transaction->getLedger () >= 4594095) - { - meta[jss::delivered_amount] = - serializedTx->getFieldAmount (sfAmount).getJson (1); - return; - } - - // If the ledger closed long after the DeliveredAmount code was deployed - // then its absence indicates that the amount delivered is listed in the - // Amount field. DeliveredAmount went live January 24, 2014. - using namespace std::chrono_literals; - auto const ct = - context.ledgerMaster.getCloseTimeBySeq (transaction->getLedger ()); - if (ct && (*ct > NetClock::time_point{446000000s})) - { - // 446000000 is in Feb 2014, well after DeliveredAmount went live - meta[jss::delivered_amount] = - serializedTx->getFieldAmount (sfAmount).getJson (1); - return; - } - } - - // Otherwise we report "unavailable" which cannot be parsed into a - // sensible amount. - meta[jss::delivered_amount] = Json::Value ("unavailable"); -} - void injectSLE(Json::Value& jv, SLE const& sle) { diff --git a/src/ripple/rpc/impl/RPCHelpers.h b/src/ripple/rpc/impl/RPCHelpers.h index 74dc6b166b..6fed508256 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/ripple/rpc/impl/RPCHelpers.h @@ -86,10 +86,6 @@ lookupLedger (std::shared_ptr&, Context&, Json::Value& result); hash_set parseAccountIds(Json::Value const& jvArray); -void -addPaymentDeliveredAmount(Json::Value&, Context&, - std::shared_ptr, TxMeta::pointer); - /** Inject JSON describing ledger entry Effects: diff --git a/src/ripple/unity/rpcx2.cpp b/src/ripple/unity/rpcx2.cpp index a4082b1092..1022a906da 100644 --- a/src/ripple/unity/rpcx2.cpp +++ b/src/ripple/unity/rpcx2.cpp @@ -50,6 +50,7 @@ #include #include +#include #include #include #include diff --git a/src/test/rpc/DeliveredAmount_test.cpp b/src/test/rpc/DeliveredAmount_test.cpp new file mode 100644 index 0000000000..ad79b2f0ee --- /dev/null +++ b/src/test/rpc/DeliveredAmount_test.cpp @@ -0,0 +1,341 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +// Helper class to track the expected number `delivered_amount` results. +class CheckDeliveredAmount +{ + // If the test occurs before or after the switch time + bool afterSwitchTime_; + // number of payments expected 'delivered_amount' available + int numExpectedAvailable_ = 0; + // Number of payments with field with `delivered_amount` set to the + // string "unavailable" + int numExpectedSetUnavailable_ = 0; + // Number of payments with no `delivered_amount` field + int numExpectedNotSet_ = 0; + + // Increment one of the expected numExpected{Available_, Unavailable_, NotSet_} values. + // Which value to increment depends on: + // 1) If the ledger is before or after the switch time + // 2) If the tx is a partial payment + // 3) If the payment is successful or not + void + adjCounters(bool success, bool partial) + { + if (!success) + { + ++numExpectedNotSet_; + return; + } + if (!afterSwitchTime_) + { + if (partial) + ++numExpectedAvailable_; + else + ++numExpectedSetUnavailable_; + return; + } + // normal case: after switch time & successful transaction + ++numExpectedAvailable_; + } + +public: + explicit CheckDeliveredAmount(bool afterSwitchTime) + : afterSwitchTime_(afterSwitchTime) + { + } + + void + adjCountersSuccess() + { + adjCounters(true, false); + } + + void + adjCountersFail() + { + adjCounters(false, false); + } + void + adjCountersPartialPayment() + { + adjCounters(true, true); + } + + // After all the txns are checked, all the `numExpected` variables should be + // zero. The `checkTxn` function decrements these variables. + bool + checkExpectedCounters() const + { + return !numExpectedAvailable_ && !numExpectedNotSet_ && + !numExpectedSetUnavailable_; + } + + // Check if the transaction has `delivered_amount` in the metaData as + // expected from our rules. Decrements the appropriate `numExpected` + // variable. After all the txns are checked, all the `numExpected` variables + // should be zero. + bool + checkTxn(Json::Value const& t, Json::Value const& metaData) + { + if (t[jss::TransactionType].asString() != "Payment") + return true; + + bool isSet = metaData.isMember(jss::delivered_amount); + bool isSetUnavailable = false; + bool isSetAvailable = false; + if (isSet) + { + if (metaData[jss::delivered_amount] != "unavailable") + isSetAvailable = true; + else + isSetUnavailable = true; + } + if (isSetAvailable) + --numExpectedAvailable_; + else if (isSetUnavailable) + --numExpectedSetUnavailable_; + else if (!isSet) + --numExpectedNotSet_; + + if (isSet) + { + if (metaData.isMember(sfDeliveredAmount.jsonName)) + { + if (metaData[jss::delivered_amount] != + metaData[sfDeliveredAmount.jsonName]) + return false; + } + else + { + if (afterSwitchTime_) + { + if (metaData[jss::delivered_amount] != t[jss::Amount]) + return false; + } + else + { + if (metaData[jss::delivered_amount] != "unavailable") + return false; + } + } + } + + if (metaData[sfTransactionResult.jsonName] != "tesSUCCESS") + { + if (isSet) + return false; + } + else + { + if (afterSwitchTime_) + { + if (!isSetAvailable) + return false; + } + else + { + if (metaData.isMember(sfDeliveredAmount.jsonName)) + { + if (!isSetAvailable) + return false; + } + else + { + if (!isSetUnavailable) + return false; + } + } + } + return true; + } +}; + +class DeliveredAmount_test : public beast::unit_test::suite +{ + void + testAccountDeliveredAmountSubscribe() + { + testcase("Ledger Request Subscribe DeliveredAmount"); + + using namespace test::jtx; + using namespace std::chrono_literals; + + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + auto const gw = Account("gateway"); + auto const USD = gw["USD"]; + + for (bool const afterSwitchTime : {true, false}) + { + Env env{*this}; + env.fund(XRP(10000), alice, bob, carol, gw); + env.trust(USD(1000), alice, bob, carol); + if (afterSwitchTime) + env.close(NetClock::time_point{446000000s}); + else + env.close(); + + CheckDeliveredAmount checkDeliveredAmount{afterSwitchTime}; + { + // add payments, but do no close until subscribed + + // normal payments + env(pay(gw, alice, USD(50))); + checkDeliveredAmount.adjCountersSuccess(); + env(pay(gw, alice, XRP(50))); + checkDeliveredAmount.adjCountersSuccess(); + + // partial payment + env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment)); + checkDeliveredAmount.adjCountersPartialPayment(); + env.require(balance(bob, USD(1000))); + + // failed payment + env(pay(bob, carol, USD(9999999)), ter(tecPATH_PARTIAL)); + checkDeliveredAmount.adjCountersFail(); + env.require(balance(carol, USD(0))); + } + + auto wsc = makeWSClient(env.app().config()); + + { + Json::Value stream; + // RPC subscribe to ledger stream + stream[jss::streams] = Json::arrayValue; + stream[jss::streams].append("ledger"); + stream[jss::accounts] = Json::arrayValue; + stream[jss::accounts].append(toBase58(alice.id())); + stream[jss::accounts].append(toBase58(bob.id())); + stream[jss::accounts].append(toBase58(carol.id())); + auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT( + jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT( + jv.isMember(jss::ripplerpc) && + jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 3); + } + { + env.close(); + // Check stream update + while (true) + { + auto const r = wsc->findMsg(1s, [&](auto const& jv) { + return jv[jss::ledger_index] == 4; + }); + if (!r) + break; + + if (!r->isMember(jss::transaction)) + continue; + + BEAST_EXPECT(checkDeliveredAmount.checkTxn( + (*r)[jss::transaction], (*r)[jss::meta])); + } + } + BEAST_EXPECT(checkDeliveredAmount.checkExpectedCounters()); + } + } + void + testTxDeliveredAmountRPC() + { + testcase("Ledger Request RPC DeliveredAmount"); + + using namespace test::jtx; + using namespace std::chrono_literals; + + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + auto const gw = Account("gateway"); + auto const USD = gw["USD"]; + + for (bool const afterSwitchTime : {true, false}) + { + Env env{*this}; + env.fund(XRP(10000), alice, bob, carol, gw); + env.trust(USD(1000), alice, bob, carol); + if (afterSwitchTime) + env.close(NetClock::time_point{446000000s}); + else + env.close(); + + CheckDeliveredAmount checkDeliveredAmount{afterSwitchTime}; + // normal payments + env(pay(gw, alice, USD(50))); + checkDeliveredAmount.adjCountersSuccess(); + env(pay(gw, alice, XRP(50))); + checkDeliveredAmount.adjCountersSuccess(); + + // partial payment + env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment)); + checkDeliveredAmount.adjCountersPartialPayment(); + env.require(balance(bob, USD(1000))); + + // failed payment + env(pay(gw, carol, USD(9999999)), ter(tecPATH_PARTIAL)); + checkDeliveredAmount.adjCountersFail(); + env.require(balance(carol, USD(0))); + + env.close(); + std::string index; + Json::Value jvParams; + jvParams[jss::ledger_index] = 4u; + jvParams[jss::transactions] = true; + jvParams[jss::expand] = true; + auto const jtxn = env.rpc( + "json", + "ledger", + to_string( + jvParams))[jss::result][jss::ledger][jss::transactions]; + for (auto const& t : jtxn) + BEAST_EXPECT( + checkDeliveredAmount.checkTxn(t, t[jss::metaData])); + BEAST_EXPECT(checkDeliveredAmount.checkExpectedCounters()); + } + } + +public: + void + run() override + { + testTxDeliveredAmountRPC(); + testAccountDeliveredAmountSubscribe(); + } +}; + +BEAST_DEFINE_TESTSUITE(DeliveredAmount, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index bc4d1e118d..3078656e8b 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -651,6 +651,7 @@ public: } + void run() override { testServer(); diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp index b26a47baa3..ff8dc138fb 100644 --- a/src/test/unity/rpc_test_unity.cpp +++ b/src/test/unity/rpc_test_unity.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include