From 97f60813903f5f0b876d948d24fa156a9c96a15c Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Thu, 22 Jul 2021 20:30:44 +0000 Subject: [PATCH] delivered amount --- src/backend/CassandraBackend.cpp | 1 - src/handlers/RPCHelpers.cpp | 153 ++++++++++++++++++------ src/handlers/RPCHelpers.h | 11 +- src/handlers/methods/impl/AccountTx.cpp | 7 +- src/handlers/methods/impl/Ledger.cpp | 44 +++---- src/handlers/methods/impl/Tx.cpp | 24 ++-- 6 files changed, 160 insertions(+), 80 deletions(-) diff --git a/src/backend/CassandraBackend.cpp b/src/backend/CassandraBackend.cpp index 3674ace3..2666fc23 100644 --- a/src/backend/CassandraBackend.cpp +++ b/src/backend/CassandraBackend.cpp @@ -7,7 +7,6 @@ template void processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func) { - BOOST_LOG_TRIVIAL(debug) << __func__ << " Processing async write response"; CassandraBackend const& backend = *requestParams.backend; auto rc = cass_future_error_code(fut); if (rc != CASS_OK) diff --git a/src/handlers/RPCHelpers.cpp b/src/handlers/RPCHelpers.cpp index 9c3568c1..b67dfbde 100644 --- a/src/handlers/RPCHelpers.cpp +++ b/src/handlers/RPCHelpers.cpp @@ -1,6 +1,56 @@ +#include #include #include -#include + +std::optional +getDeliveredAmount( + std::shared_ptr const& txn, + std::shared_ptr 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 const& txn, + std::shared_ptr 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 accountFromStringStrict(std::string const& account) @@ -76,6 +126,23 @@ toJson(ripple::STBase const& obj) return value.as_object(); } +std::pair +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 toJson(ripple::TxMeta const& meta) { @@ -88,9 +155,8 @@ toJson(ripple::TxMeta const& meta) boost::json::value toBoostJson(Json::Value const& value) { - boost::json::value boostValue = - boost::json::parse(value.toStyledString()); - + boost::json::value boostValue = boost::json::parse(value.toStyledString()); + return boostValue; } @@ -138,14 +204,15 @@ ledgerInfoFromRequest(RPC::Context const& ctx) if (!hashValue.is_null()) { if (!hashValue.is_string()) - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"}; + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"}; ripple::uint256 ledgerHash; 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); - } else if (!indexValue.is_null()) { @@ -155,15 +222,14 @@ ledgerInfoFromRequest(RPC::Context const& ctx) else if (!indexValue.is_string() && indexValue.is_int64()) ledgerSequence = indexValue.as_int64(); else - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"}; + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"}; - lgrInfo = - ctx.backend->fetchLedgerBySequence(ledgerSequence); + lgrInfo = ctx.backend->fetchLedgerBySequence(ledgerSequence); } else { - lgrInfo = - ctx.backend->fetchLedgerBySequence(ctx.range.maxSequence); + lgrInfo = ctx.backend->fetchLedgerBySequence(ctx.range.maxSequence); } if (!lgrInfo) @@ -297,13 +363,15 @@ keypairFromRequst(boost::json::object const& request) } 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) { - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, - "Exactly one of the following must be specified: " - " passphrase, secret, seed, or seed_hex"}; + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, + "Exactly one of the following must be specified: " + " passphrase, secret, seed, or seed_hex"}; } boost::optional keyType; @@ -312,18 +380,20 @@ keypairFromRequst(boost::json::object const& request) if (has_key_type) { 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(); keyType = ripple::keyTypeFromString(key_type); if (!keyType) - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "invalidFieldKeyType"}; + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, "invalidFieldKeyType"}; if (secretType == "secret") - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, - "The secret field is not allowed if key_type is used."}; - + return RPC::Status{ + 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 @@ -337,9 +407,11 @@ keypairFromRequst(boost::json::object const& request) { // If the user passed in an Ed25519 seed but *explicitly* // requested another key type, return an error. - if (keyType.value_or(ripple::KeyType::ed25519) != ripple::KeyType::ed25519) - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, - "Specified seed is for an Ed25519 wallet."}; + if (keyType.value_or(ripple::KeyType::ed25519) != + ripple::KeyType::ed25519) + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, + "Specified seed is for an Ed25519 wallet."}; keyType = ripple::KeyType::ed25519; } @@ -353,8 +425,9 @@ keypairFromRequst(boost::json::object const& request) if (has_key_type) { if (!request.at(secretType).is_string()) - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, - "secret value must be string"}; + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, + "secret value must be string"}; std::string key = request.at(secretType).as_string().c_str(); @@ -372,8 +445,9 @@ keypairFromRequst(boost::json::object const& request) else { if (!request.at("secret").is_string()) - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, - "field secret should be a string"}; + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, + "field secret should be a string"}; std::string secret = request.at("secret").as_string().c_str(); seed = ripple::parseGenericSeed(secret); @@ -381,13 +455,15 @@ keypairFromRequst(boost::json::object const& request) } if (!seed) - return RPC::Status{RPC::Error::rpcBAD_SEED, - "Bad Seed: invalid field message secretType"}; + return RPC::Status{ + RPC::Error::rpcBAD_SEED, + "Bad Seed: invalid field message secretType"}; - if (keyType != ripple::KeyType::secp256k1 - && keyType != ripple::KeyType::ed25519) - return RPC::Status{RPC::Error::rpcINVALID_PARAMS, - "keypairForSignature: invalid key type"}; + if (keyType != ripple::KeyType::secp256k1 && + keyType != ripple::KeyType::ed25519) + return RPC::Status{ + RPC::Error::rpcINVALID_PARAMS, + "keypairForSignature: invalid key type"}; return generateKeyPair(*keyType, *seed); } @@ -433,7 +509,7 @@ isGlobalFrozen( ripple::SerialIter it{blob->data(), blob->size()}; ripple::SLE sle{it, key}; - + return sle.isFlag(ripple::lsfGlobalFreeze); } @@ -456,7 +532,7 @@ isFrozen( ripple::SerialIter it{blob->data(), blob->size()}; ripple::SLE sle{it, key}; - + if (sle.isFlag(ripple::lsfGlobalFreeze)) return true; @@ -471,7 +547,7 @@ isFrozen( ripple::SerialIter issuerIt{blob->data(), blob->size()}; ripple::SLE issuerLine{it, key}; - auto frozen = + auto frozen = (issuer > account) ? ripple::lsfHighFreeze : ripple::lsfLowFreeze; if (issuerLine.isFlag(frozen)) @@ -498,7 +574,8 @@ xrpLiquid( 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); diff --git a/src/handlers/RPCHelpers.h b/src/handlers/RPCHelpers.h index c6181515..647a477b 100644 --- a/src/handlers/RPCHelpers.h +++ b/src/handlers/RPCHelpers.h @@ -2,15 +2,15 @@ #ifndef XRPL_REPORTING_RPCHELPERS_H_INCLUDED #define XRPL_REPORTING_RPCHELPERS_H_INCLUDED -#include #include #include -#include +#include #include +#include #include -#include -#include #include +#include +#include std::optional accountFromStringStrict(std::string const& account); @@ -27,6 +27,9 @@ deserializeTxPlusMeta( Backend::TransactionAndMetadata const& blobs, std::uint32_t seq); +std::pair +toExpandedJson(Backend::TransactionAndMetadata const& blobs); + boost::json::object toJson(ripple::STBase const& obj); diff --git a/src/handlers/methods/impl/AccountTx.cpp b/src/handlers/methods/impl/AccountTx.cpp index 5253dfb5..10f7c606 100644 --- a/src/handlers/methods/impl/AccountTx.cpp +++ b/src/handlers/methods/impl/AccountTx.cpp @@ -194,11 +194,10 @@ doAccountTx(Context const& context) if (!binary) { - auto [txn, meta] = deserializeTxPlusMeta(txnPlusMeta); - obj["meta"] = toJson(*meta); - obj["tx"] = toJson(*txn); + auto [txn, meta] = toExpandedJson(txnPlusMeta); + obj["meta"] = meta; + obj["tx"] = txn; obj["tx"].as_object()["ledger_index"] = txnPlusMeta.ledgerSequence; - obj["tx"].as_object()["inLedger"] = txnPlusMeta.ledgerSequence; } else { diff --git a/src/handlers/methods/impl/Ledger.cpp b/src/handlers/methods/impl/Ledger.cpp index f15da4b7..a33290b9 100644 --- a/src/handlers/methods/impl/Ledger.cpp +++ b/src/handlers/methods/impl/Ledger.cpp @@ -1,9 +1,8 @@ -#include -#include #include +#include +#include -namespace RPC -{ +namespace RPC { Result doLedger(Context const& context) @@ -12,29 +11,29 @@ doLedger(Context const& context) boost::json::object response = {}; 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"}; - + binary = params.at("binary").as_bool(); } 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"}; - + transactions = params.at("transactions").as_bool(); } 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"}; - + expand = params.at("expand").as_bool(); } @@ -55,7 +54,8 @@ doLedger(Context const& context) header["account_hash"] = ripple::strHex(lgrInfo.accountHash); header["close_flags"] = lgrInfo.closeFlags; 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["closed"] = true; header["hash"] = ripple::strHex(lgrInfo.hash); @@ -69,6 +69,7 @@ doLedger(Context const& context) header["total_coins"] = ripple::to_string(lgrInfo.drops); header["transaction_hash"] = ripple::strHex(lgrInfo.txHash); } + header["closed"] = true; if (transactions) { @@ -76,7 +77,8 @@ doLedger(Context const& context) boost::json::array& jsonTxs = header.at("transactions").as_array(); if (expand) { - auto txns = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq); + auto txns = + context.backend->fetchAllTransactionsInLedger(lgrInfo.seq); std::transform( std::move_iterator(txns.begin()), @@ -86,16 +88,16 @@ doLedger(Context const& context) boost::json::object entry; if (!binary) { - auto [sttx, meta] = deserializeTxPlusMeta(obj); - entry = toJson(*sttx); - entry["metaData"] = toJson(*meta); + auto [txn, meta] = toExpandedJson(obj); + entry = txn; + entry["metaData"] = meta; } else { entry["tx_blob"] = ripple::strHex(obj.transaction); entry["meta"] = ripple::strHex(obj.metadata); } - entry["ledger_sequence"] = obj.ledgerSequence; + entry["ledger_index"] = obj.ledgerSequence; return entry; }); } @@ -113,11 +115,11 @@ doLedger(Context const& context) }); } } - + response["ledger"] = header; response["ledger_hash"] = ripple::strHex(lgrInfo.hash); response["ledger_index"] = lgrInfo.seq; return response; } -} \ No newline at end of file +} // namespace RPC diff --git a/src/handlers/methods/impl/Tx.cpp b/src/handlers/methods/impl/Tx.cpp index 9ce66d37..6a46a91d 100644 --- a/src/handlers/methods/impl/Tx.cpp +++ b/src/handlers/methods/impl/Tx.cpp @@ -17,13 +17,12 @@ */ //============================================================================== -#include -#include #include #include +#include +#include -namespace RPC -{ +namespace RPC { // { // transaction: @@ -37,7 +36,7 @@ doTx(Context const& context) if (!request.contains("transaction")) return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"}; - + if (!request.at("transaction").is_string()) return Status{Error::rpcINVALID_PARAMS, "transactionNotString"}; @@ -46,11 +45,11 @@ doTx(Context const& context) return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"}; 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"}; - + binary = request.at("binary").as_bool(); } @@ -64,9 +63,10 @@ doTx(Context const& context) if (!binary) { - auto [sttx, meta] = deserializeTxPlusMeta(dbResponse.value()); - response = toJson(*sttx); - response["meta"] = toJson(*meta); + auto [txn, meta] = toExpandedJson(*dbResponse); + response = txn; + response["meta"] = meta; + response["ledger_index"] = dbResponse->ledgerSequence; } else { @@ -77,4 +77,4 @@ doTx(Context const& context) return response; } -} // namespace RPC \ No newline at end of file +} // namespace RPC