delivered amount

This commit is contained in:
CJ Cobb
2021-07-22 20:30:44 +00:00
parent 41bd820a2a
commit 97f6081390
6 changed files with 160 additions and 80 deletions

View File

@@ -7,7 +7,6 @@ template <class T, class F>
void void
processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func) processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " Processing async write response";
CassandraBackend const& backend = *requestParams.backend; CassandraBackend const& backend = *requestParams.backend;
auto rc = cass_future_error_code(fut); auto rc = cass_future_error_code(fut);
if (rc != CASS_OK) if (rc != CASS_OK)

View File

@@ -1,6 +1,56 @@
#include <backend/BackendInterface.h>
#include <handlers/RPCHelpers.h> #include <handlers/RPCHelpers.h>
#include <handlers/Status.h> #include <handlers/Status.h>
#include <backend/BackendInterface.h>
std::optional<ripple::STAmount>
getDeliveredAmount(
std::shared_ptr<ripple::STTx const> const& txn,
std::shared_ptr<ripple::STObject const> const& meta,
uint32_t ledgerSequence)
{
if (meta->isFieldPresent(ripple::sfDeliveredAmount))
return meta->getFieldAmount(ripple::sfDeliveredAmount);
if (txn->isFieldPresent(ripple::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 (ledgerSequence >= 4594095)
{
return txn->getFieldAmount(ripple::sfAmount);
}
}
return {};
}
bool
canHaveDeliveredAmount(
std::shared_ptr<ripple::STTx const> const& txn,
std::shared_ptr<ripple::STObject const> const& meta)
{
ripple::TxType const tt{txn->getTxnType()};
if (tt != ripple::ttPAYMENT && tt != ripple::ttCHECK_CASH &&
tt != ripple::ttACCOUNT_DELETE)
return false;
/*
if (tt == ttCHECK_CASH && !getFix1623Enabled())
return false;
*/
if (ripple::TER::fromInt(meta->getFieldU8(ripple::sfTransactionResult)) !=
ripple::tesSUCCESS)
return false;
return true;
}
std::optional<ripple::AccountID> std::optional<ripple::AccountID>
accountFromStringStrict(std::string const& account) accountFromStringStrict(std::string const& account)
@@ -76,6 +126,23 @@ toJson(ripple::STBase const& obj)
return value.as_object(); return value.as_object();
} }
std::pair<boost::json::object, boost::json::object>
toExpandedJson(Backend::TransactionAndMetadata const& blobs)
{
auto [txn, meta] = deserializeTxPlusMeta(blobs);
auto txnJson = toJson(*txn);
auto metaJson = toJson(*meta);
if (canHaveDeliveredAmount(txn, meta))
{
if (auto amt = getDeliveredAmount(txn, meta, blobs.ledgerSequence))
metaJson["delivered_amount"] =
toBoostJson(amt->getJson(ripple::JsonOptions::include_date));
else
metaJson["delivered_amount"] = "unavailable";
}
return {txnJson, metaJson};
}
boost::json::object boost::json::object
toJson(ripple::TxMeta const& meta) toJson(ripple::TxMeta const& meta)
{ {
@@ -88,9 +155,8 @@ toJson(ripple::TxMeta const& meta)
boost::json::value boost::json::value
toBoostJson(Json::Value const& value) toBoostJson(Json::Value const& value)
{ {
boost::json::value boostValue = boost::json::value boostValue = boost::json::parse(value.toStyledString());
boost::json::parse(value.toStyledString());
return boostValue; return boostValue;
} }
@@ -138,14 +204,15 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
if (!hashValue.is_null()) if (!hashValue.is_null())
{ {
if (!hashValue.is_string()) if (!hashValue.is_string())
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"}; return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
ripple::uint256 ledgerHash; ripple::uint256 ledgerHash;
if (!ledgerHash.parseHex(hashValue.as_string().c_str())) if (!ledgerHash.parseHex(hashValue.as_string().c_str()))
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"}; return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"};
lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash); lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash);
} }
else if (!indexValue.is_null()) else if (!indexValue.is_null())
{ {
@@ -155,15 +222,14 @@ ledgerInfoFromRequest(RPC::Context const& ctx)
else if (!indexValue.is_string() && indexValue.is_int64()) else if (!indexValue.is_string() && indexValue.is_int64())
ledgerSequence = indexValue.as_int64(); ledgerSequence = indexValue.as_int64();
else else
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"}; return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
lgrInfo = lgrInfo = ctx.backend->fetchLedgerBySequence(ledgerSequence);
ctx.backend->fetchLedgerBySequence(ledgerSequence);
} }
else else
{ {
lgrInfo = lgrInfo = ctx.backend->fetchLedgerBySequence(ctx.range.maxSequence);
ctx.backend->fetchLedgerBySequence(ctx.range.maxSequence);
} }
if (!lgrInfo) if (!lgrInfo)
@@ -297,13 +363,15 @@ keypairFromRequst(boost::json::object const& request)
} }
if (count == 0) if (count == 0)
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "missing field secret"}; return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "missing field secret"};
if (count > 1) if (count > 1)
{ {
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, return RPC::Status{
"Exactly one of the following must be specified: " RPC::Error::rpcINVALID_PARAMS,
" passphrase, secret, seed, or seed_hex"}; "Exactly one of the following must be specified: "
" passphrase, secret, seed, or seed_hex"};
} }
boost::optional<ripple::KeyType> keyType; boost::optional<ripple::KeyType> keyType;
@@ -312,18 +380,20 @@ keypairFromRequst(boost::json::object const& request)
if (has_key_type) if (has_key_type)
{ {
if (!request.at("key_type").is_string()) if (!request.at("key_type").is_string())
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "keyTypeNotString"}; return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "keyTypeNotString"};
std::string key_type = request.at("key_type").as_string().c_str(); std::string key_type = request.at("key_type").as_string().c_str();
keyType = ripple::keyTypeFromString(key_type); keyType = ripple::keyTypeFromString(key_type);
if (!keyType) if (!keyType)
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "invalidFieldKeyType"}; return RPC::Status{
RPC::Error::rpcINVALID_PARAMS, "invalidFieldKeyType"};
if (secretType == "secret") if (secretType == "secret")
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, return RPC::Status{
"The secret field is not allowed if key_type is used."}; RPC::Error::rpcINVALID_PARAMS,
"The secret field is not allowed if key_type is used."};
} }
// ripple-lib encodes seed used to generate an Ed25519 wallet in a // ripple-lib encodes seed used to generate an Ed25519 wallet in a
@@ -337,9 +407,11 @@ keypairFromRequst(boost::json::object const& request)
{ {
// If the user passed in an Ed25519 seed but *explicitly* // If the user passed in an Ed25519 seed but *explicitly*
// requested another key type, return an error. // requested another key type, return an error.
if (keyType.value_or(ripple::KeyType::ed25519) != ripple::KeyType::ed25519) if (keyType.value_or(ripple::KeyType::ed25519) !=
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, ripple::KeyType::ed25519)
"Specified seed is for an Ed25519 wallet."}; return RPC::Status{
RPC::Error::rpcINVALID_PARAMS,
"Specified seed is for an Ed25519 wallet."};
keyType = ripple::KeyType::ed25519; keyType = ripple::KeyType::ed25519;
} }
@@ -353,8 +425,9 @@ keypairFromRequst(boost::json::object const& request)
if (has_key_type) if (has_key_type)
{ {
if (!request.at(secretType).is_string()) if (!request.at(secretType).is_string())
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, return RPC::Status{
"secret value must be string"}; RPC::Error::rpcINVALID_PARAMS,
"secret value must be string"};
std::string key = request.at(secretType).as_string().c_str(); std::string key = request.at(secretType).as_string().c_str();
@@ -372,8 +445,9 @@ keypairFromRequst(boost::json::object const& request)
else else
{ {
if (!request.at("secret").is_string()) if (!request.at("secret").is_string())
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, return RPC::Status{
"field secret should be a string"}; RPC::Error::rpcINVALID_PARAMS,
"field secret should be a string"};
std::string secret = request.at("secret").as_string().c_str(); std::string secret = request.at("secret").as_string().c_str();
seed = ripple::parseGenericSeed(secret); seed = ripple::parseGenericSeed(secret);
@@ -381,13 +455,15 @@ keypairFromRequst(boost::json::object const& request)
} }
if (!seed) if (!seed)
return RPC::Status{RPC::Error::rpcBAD_SEED, return RPC::Status{
"Bad Seed: invalid field message secretType"}; RPC::Error::rpcBAD_SEED,
"Bad Seed: invalid field message secretType"};
if (keyType != ripple::KeyType::secp256k1 if (keyType != ripple::KeyType::secp256k1 &&
&& keyType != ripple::KeyType::ed25519) keyType != ripple::KeyType::ed25519)
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, return RPC::Status{
"keypairForSignature: invalid key type"}; RPC::Error::rpcINVALID_PARAMS,
"keypairForSignature: invalid key type"};
return generateKeyPair(*keyType, *seed); return generateKeyPair(*keyType, *seed);
} }
@@ -433,7 +509,7 @@ isGlobalFrozen(
ripple::SerialIter it{blob->data(), blob->size()}; ripple::SerialIter it{blob->data(), blob->size()};
ripple::SLE sle{it, key}; ripple::SLE sle{it, key};
return sle.isFlag(ripple::lsfGlobalFreeze); return sle.isFlag(ripple::lsfGlobalFreeze);
} }
@@ -456,7 +532,7 @@ isFrozen(
ripple::SerialIter it{blob->data(), blob->size()}; ripple::SerialIter it{blob->data(), blob->size()};
ripple::SLE sle{it, key}; ripple::SLE sle{it, key};
if (sle.isFlag(ripple::lsfGlobalFreeze)) if (sle.isFlag(ripple::lsfGlobalFreeze))
return true; return true;
@@ -471,7 +547,7 @@ isFrozen(
ripple::SerialIter issuerIt{blob->data(), blob->size()}; ripple::SerialIter issuerIt{blob->data(), blob->size()};
ripple::SLE issuerLine{it, key}; ripple::SLE issuerLine{it, key};
auto frozen = auto frozen =
(issuer > account) ? ripple::lsfHighFreeze : ripple::lsfLowFreeze; (issuer > account) ? ripple::lsfHighFreeze : ripple::lsfLowFreeze;
if (issuerLine.isFlag(frozen)) if (issuerLine.isFlag(frozen))
@@ -498,7 +574,8 @@ xrpLiquid(
std::uint32_t const ownerCount = sle.getFieldU32(ripple::sfOwnerCount); std::uint32_t const ownerCount = sle.getFieldU32(ripple::sfOwnerCount);
auto const reserve = backend.fetchFees(sequence)->accountReserve(ownerCount); auto const reserve =
backend.fetchFees(sequence)->accountReserve(ownerCount);
auto const balance = sle.getFieldAmount(ripple::sfBalance); auto const balance = sle.getFieldAmount(ripple::sfBalance);

View File

@@ -2,15 +2,15 @@
#ifndef XRPL_REPORTING_RPCHELPERS_H_INCLUDED #ifndef XRPL_REPORTING_RPCHELPERS_H_INCLUDED
#define XRPL_REPORTING_RPCHELPERS_H_INCLUDED #define XRPL_REPORTING_RPCHELPERS_H_INCLUDED
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/jss.h> #include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <handlers/Status.h>
#include <handlers/Context.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <handlers/Context.h>
#include <handlers/Status.h>
std::optional<ripple::AccountID> std::optional<ripple::AccountID>
accountFromStringStrict(std::string const& account); accountFromStringStrict(std::string const& account);
@@ -27,6 +27,9 @@ deserializeTxPlusMeta(
Backend::TransactionAndMetadata const& blobs, Backend::TransactionAndMetadata const& blobs,
std::uint32_t seq); std::uint32_t seq);
std::pair<boost::json::object, boost::json::object>
toExpandedJson(Backend::TransactionAndMetadata const& blobs);
boost::json::object boost::json::object
toJson(ripple::STBase const& obj); toJson(ripple::STBase const& obj);

View File

@@ -194,11 +194,10 @@ doAccountTx(Context const& context)
if (!binary) if (!binary)
{ {
auto [txn, meta] = deserializeTxPlusMeta(txnPlusMeta); auto [txn, meta] = toExpandedJson(txnPlusMeta);
obj["meta"] = toJson(*meta); obj["meta"] = meta;
obj["tx"] = toJson(*txn); obj["tx"] = txn;
obj["tx"].as_object()["ledger_index"] = txnPlusMeta.ledgerSequence; obj["tx"].as_object()["ledger_index"] = txnPlusMeta.ledgerSequence;
obj["tx"].as_object()["inLedger"] = txnPlusMeta.ledgerSequence;
} }
else else
{ {

View File

@@ -1,9 +1,8 @@
#include <handlers/methods/Ledger.h>
#include <handlers/RPCHelpers.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <handlers/RPCHelpers.h>
#include <handlers/methods/Ledger.h>
namespace RPC namespace RPC {
{
Result Result
doLedger(Context const& context) doLedger(Context const& context)
@@ -12,29 +11,29 @@ doLedger(Context const& context)
boost::json::object response = {}; boost::json::object response = {};
bool binary = false; bool binary = false;
if(params.contains("binary")) if (params.contains("binary"))
{ {
if(!params.at("binary").is_bool()) if (!params.at("binary").is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = params.at("binary").as_bool(); binary = params.at("binary").as_bool();
} }
bool transactions = false; bool transactions = false;
if(params.contains("transactions")) if (params.contains("transactions"))
{ {
if(!params.at("transactions").is_bool()) if (!params.at("transactions").is_bool())
return Status{Error::rpcINVALID_PARAMS, "transactionsFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "transactionsFlagNotBool"};
transactions = params.at("transactions").as_bool(); transactions = params.at("transactions").as_bool();
} }
bool expand = false; bool expand = false;
if(params.contains("expand")) if (params.contains("expand"))
{ {
if(!params.at("expand").is_bool()) if (!params.at("expand").is_bool())
return Status{Error::rpcINVALID_PARAMS, "expandFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "expandFlagNotBool"};
expand = params.at("expand").as_bool(); expand = params.at("expand").as_bool();
} }
@@ -55,7 +54,8 @@ doLedger(Context const& context)
header["account_hash"] = ripple::strHex(lgrInfo.accountHash); header["account_hash"] = ripple::strHex(lgrInfo.accountHash);
header["close_flags"] = lgrInfo.closeFlags; header["close_flags"] = lgrInfo.closeFlags;
header["close_time"] = lgrInfo.closeTime.time_since_epoch().count(); header["close_time"] = lgrInfo.closeTime.time_since_epoch().count();
header["close_time_human"] = ripple::to_string(lgrInfo.closeTime);; header["close_time_human"] = ripple::to_string(lgrInfo.closeTime);
;
header["close_time_resolution"] = lgrInfo.closeTimeResolution.count(); header["close_time_resolution"] = lgrInfo.closeTimeResolution.count();
header["closed"] = true; header["closed"] = true;
header["hash"] = ripple::strHex(lgrInfo.hash); header["hash"] = ripple::strHex(lgrInfo.hash);
@@ -69,6 +69,7 @@ doLedger(Context const& context)
header["total_coins"] = ripple::to_string(lgrInfo.drops); header["total_coins"] = ripple::to_string(lgrInfo.drops);
header["transaction_hash"] = ripple::strHex(lgrInfo.txHash); header["transaction_hash"] = ripple::strHex(lgrInfo.txHash);
} }
header["closed"] = true;
if (transactions) if (transactions)
{ {
@@ -76,7 +77,8 @@ doLedger(Context const& context)
boost::json::array& jsonTxs = header.at("transactions").as_array(); boost::json::array& jsonTxs = header.at("transactions").as_array();
if (expand) if (expand)
{ {
auto txns = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq); auto txns =
context.backend->fetchAllTransactionsInLedger(lgrInfo.seq);
std::transform( std::transform(
std::move_iterator(txns.begin()), std::move_iterator(txns.begin()),
@@ -86,16 +88,16 @@ doLedger(Context const& context)
boost::json::object entry; boost::json::object entry;
if (!binary) if (!binary)
{ {
auto [sttx, meta] = deserializeTxPlusMeta(obj); auto [txn, meta] = toExpandedJson(obj);
entry = toJson(*sttx); entry = txn;
entry["metaData"] = toJson(*meta); entry["metaData"] = meta;
} }
else else
{ {
entry["tx_blob"] = ripple::strHex(obj.transaction); entry["tx_blob"] = ripple::strHex(obj.transaction);
entry["meta"] = ripple::strHex(obj.metadata); entry["meta"] = ripple::strHex(obj.metadata);
} }
entry["ledger_sequence"] = obj.ledgerSequence; entry["ledger_index"] = obj.ledgerSequence;
return entry; return entry;
}); });
} }
@@ -113,11 +115,11 @@ doLedger(Context const& context)
}); });
} }
} }
response["ledger"] = header; response["ledger"] = header;
response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
response["ledger_index"] = lgrInfo.seq; response["ledger_index"] = lgrInfo.seq;
return response; return response;
} }
} } // namespace RPC

View File

@@ -17,13 +17,12 @@
*/ */
//============================================================================== //==============================================================================
#include <handlers/RPCHelpers.h>
#include <handlers/methods/Transaction.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <backend/Pg.h> #include <backend/Pg.h>
#include <handlers/RPCHelpers.h>
#include <handlers/methods/Transaction.h>
namespace RPC namespace RPC {
{
// { // {
// transaction: <hex> // transaction: <hex>
@@ -37,7 +36,7 @@ doTx(Context const& context)
if (!request.contains("transaction")) if (!request.contains("transaction"))
return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"}; return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"};
if (!request.at("transaction").is_string()) if (!request.at("transaction").is_string())
return Status{Error::rpcINVALID_PARAMS, "transactionNotString"}; return Status{Error::rpcINVALID_PARAMS, "transactionNotString"};
@@ -46,11 +45,11 @@ doTx(Context const& context)
return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"}; return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"};
bool binary = false; bool binary = false;
if(request.contains("binary")) if (request.contains("binary"))
{ {
if(!request.at("binary").is_bool()) if (!request.at("binary").is_bool())
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"}; return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
binary = request.at("binary").as_bool(); binary = request.at("binary").as_bool();
} }
@@ -64,9 +63,10 @@ doTx(Context const& context)
if (!binary) if (!binary)
{ {
auto [sttx, meta] = deserializeTxPlusMeta(dbResponse.value()); auto [txn, meta] = toExpandedJson(*dbResponse);
response = toJson(*sttx); response = txn;
response["meta"] = toJson(*meta); response["meta"] = meta;
response["ledger_index"] = dbResponse->ledgerSequence;
} }
else else
{ {
@@ -77,4 +77,4 @@ doTx(Context const& context)
return response; return response;
} }
} // namespace RPC } // namespace RPC