Add delivered amount to the ledger RPC command

This commit is contained in:
seelabs
2019-02-20 11:39:05 -05:00
committed by Nik Bougalis
parent 9dbf8495ee
commit c5d215d901
22 changed files with 695 additions and 166 deletions

View File

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

View File

@@ -35,8 +35,8 @@ AcceptedLedgerTx::AcceptedLedgerTx (
: mLedger (ledger)
, mTxn (txn)
, mMeta (std::make_shared<TxMeta> (
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)
{

View File

@@ -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<TxMeta>
{
auto meta = ledger.txRead(txID).second;
if (!meta)
return {};
return std::make_shared<TxMeta> (txID, ledger.seq(), *meta, j);
return std::make_shared<TxMeta> (txID, ledger.seq(), *meta);
};
auto validMetaData = getMeta (validLedger, tx);

View File

@@ -21,6 +21,8 @@
#include <ripple/app/misc/TxQ.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/date.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
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<STTx const>,
std::shared_ptr<STObject const>> const i)
Json::Value
fillJsonTx(
LedgerFill const& fill,
bool bBinary,
bool bExpanded,
std::shared_ptr<STTx const> const& txn,
std::shared_ptr<STObject const> 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<TxMeta>(
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 <class Object>
@@ -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);
}
}

View File

@@ -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 ()));
}

View File

@@ -53,6 +53,7 @@
#include <ripple/overlay/predicates.h>
#include <ripple/protocol/BuildInfo.h>
#include <ripple/resource/ResourceManager.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/beast/rfc2616.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/beast/utility/rngfill.h>
@@ -1963,9 +1964,10 @@ NetworkOPs::AccountTxs NetworkOPsImp::getAccountTxs (
}
if (txn)
ret.emplace_back (txn, std::make_shared<TxMeta> (
txn->getID (), txn->getLedger (), txnMeta,
app_.journal("TxMeta")));
ret.emplace_back(
txn,
std::make_shared<TxMeta>(
txn->getID(), txn->getLedger(), txnMeta));
}
}
@@ -2556,9 +2558,16 @@ void NetworkOPsImp::pubValidatedTransaction (
std::shared_ptr<ReadView const> const& alAccepted,
const AcceptedLedgerTx& alTx)
{
std::shared_ptr<STTx const> 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<STTx const> 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);

View File

@@ -48,7 +48,7 @@ convertBlobsToTxResult (
tr->setLedger (ledger_index);
auto metaset = std::make_shared<TxMeta> (
tr->getID (), tr->getLedger (), rawMeta, app.journal ("TxMeta"));
tr->getID (), tr->getLedger (), rawMeta);
to.emplace_back(std::move(tr), metaset);
};

View File

@@ -42,29 +42,26 @@ private:
explicit CtorHelper() = default;
};
template<class T>
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<std::uint32_t> (-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<std::uint32_t> (-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<AccountID>
getAffectedAccounts() const;
getAffectedAccounts(beast::Journal j) const;
Json::Value getJson (int p) const
{
@@ -144,8 +141,6 @@ private:
boost::optional <STAmount> mDelivered;
STArray mNodes;
beast::Journal j_;
};
} // ripple

View File

@@ -116,7 +116,7 @@ ApplyStateTable::apply (OpenView& to,
std::shared_ptr<Serializer> sMeta;
if (!to.open())
{
TxMeta meta(j);
TxMeta meta;
// VFALCO Shouldn't TxMeta ctor do this?
meta.init (tx.getTransactionID(), to.seq());
if (deliver)

View File

@@ -30,11 +30,10 @@ namespace ripple {
template<class T>
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<AccountID>
TxMeta::getAffectedAccounts() const
TxMeta::getAffectedAccounts(beast::Journal j) const
{
boost::container::flat_set<AccountID> 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);
}
}
}

View File

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

View File

@@ -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 <memory>
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<STTx const> serializedTx,
TxMeta const&);
void
insertDeliveredAmount(
Json::Value& meta,
Context&,
std::shared_ptr<Transaction>,
TxMeta const&);
/** @} */
} // RPC
} // ripple
#endif

View File

@@ -29,6 +29,7 @@
#include <ripple/protocol/UintTypes.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/Role.h>
@@ -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 ();

View File

@@ -27,6 +27,7 @@
#include <ripple/protocol/JsonFields.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/Role.h>
@@ -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]

View File

@@ -26,6 +26,7 @@
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/impl/RPCHelpers.h>
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<TxMeta> (txn->getID (),
lgr->seq (), *rawMeta, context.app.journal ("TxMeta"));
auto txMeta = std::make_shared<TxMeta>(
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);
}
}

View File

@@ -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 <ripple/rpc/DeliveredAmount.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/app/misc/Transaction.h>
#include <ripple/ledger/View.h>
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Feature.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <boost/algorithm/string/case_conv.hpp>
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<NetClock::time_point>
*/
template<class GetFix1623Enabled, class GetLedgerIndex, class GetCloseTime>
void
insertDeliveredAmount(
Json::Value& meta,
GetFix1623Enabled const& getFix1623Enabled,
GetLedgerIndex const& getLedgerIndex,
GetCloseTime const& getCloseTime,
std::shared_ptr<STTx const> 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<STTx const> 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> 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<NetClock::time_point> {
return context.ledgerMaster.getCloseTimeBySeq(transaction->getLedger());
};
insertDeliveredAmount(
meta,
getFix1623Enabled,
getLedgerIndex,
getCloseTime,
std::move(serializedTx),
transactionMeta);
}
} // RPC
} // ripple

View File

@@ -25,6 +25,7 @@
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Feature.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <boost/algorithm/string/case_conv.hpp>
@@ -393,86 +394,6 @@ parseAccountIds(Json::Value const& jvArray)
return result;
}
void
addPaymentDeliveredAmount(Json::Value& meta, RPC::Context& context,
std::shared_ptr<Transaction> 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)
{

View File

@@ -86,10 +86,6 @@ lookupLedger (std::shared_ptr<ReadView const>&, Context&, Json::Value& result);
hash_set <AccountID>
parseAccountIds(Json::Value const& jvArray);
void
addPaymentDeliveredAmount(Json::Value&, Context&,
std::shared_ptr<Transaction>, TxMeta::pointer);
/** Inject JSON describing ledger entry
Effects:

View File

@@ -50,6 +50,7 @@
#include <ripple/rpc/handlers/ValidatorListSites.cpp>
#include <ripple/rpc/handlers/WalletPropose.cpp>
#include <ripple/rpc/impl/DeliveredAmount.cpp>
#include <ripple/rpc/impl/Handler.cpp>
#include <ripple/rpc/impl/LegacyPathFind.cpp>
#include <ripple/rpc/impl/Role.cpp>

View File

@@ -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 <ripple/beast/unit_test.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/JsonFields.h>
#include <test/jtx.h>
#include <test/jtx/WSClient.h>
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

View File

@@ -651,6 +651,7 @@ public:
}
void run() override
{
testServer();

View File

@@ -28,6 +28,7 @@
#include <test/rpc/AmendmentBlocked_test.cpp>
#include <test/rpc/Book_test.cpp>
#include <test/rpc/DepositAuthorized_test.cpp>
#include <test/rpc/DeliveredAmount_test.cpp>
#include <test/rpc/Feature_test.cpp>
#include <test/rpc/GatewayBalances_test.cpp>
#include <test/rpc/GetCounts_test.cpp>