mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-19 01:55:48 +00:00
APIv2(DeliverMax): add alias for Amount in Payment transactions (#4733)
Using the "Amount" field in Payment transactions can cause incorrect interpretation. There continue to be problems from the use of this field. "Amount" is rarely the correct field to use; instead, "delivered_amount" (or "DeliveredAmount") should be used. Rename the "Amount" field to "DeliverMax", a less misleading name. With api_version: 2, remove the "Amount" field from Payment transactions. - Input: "DeliverMax" in `tx_json` is an alias for "Amount" - sign - submit (in sign-and-submit mode) - submit_multisigned - sign_for - Output: Add "DeliverMax" where transactions are provided by the API - ledger - tx - tx_history - account_tx - transaction_entry - subscribe (transactions stream) - Output: Remove "Amount" from API version 2 Fix #3484 Fix #3902
This commit is contained in:
@@ -227,6 +227,7 @@ install (
|
||||
install (
|
||||
FILES
|
||||
src/ripple/json/JsonPropertyStream.h
|
||||
src/ripple/json/MultivarJson.h
|
||||
src/ripple/json/Object.h
|
||||
src/ripple/json/Output.h
|
||||
src/ripple/json/Writer.h
|
||||
@@ -478,6 +479,7 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/misc/detail/impl/WorkSSL.cpp
|
||||
src/ripple/app/misc/impl/AccountTxPaging.cpp
|
||||
src/ripple/app/misc/impl/AmendmentTable.cpp
|
||||
src/ripple/app/misc/impl/DeliverMax.cpp
|
||||
src/ripple/app/misc/impl/LoadFeeTrack.cpp
|
||||
src/ripple/app/misc/impl/Manifest.cpp
|
||||
src/ripple/app/misc/impl/Transaction.cpp
|
||||
@@ -957,6 +959,7 @@ if (tests)
|
||||
src/test/json/Output_test.cpp
|
||||
src/test/json/Writer_test.cpp
|
||||
src/test/json/json_value_test.cpp
|
||||
src/test/json/MultivarJson_test.cpp
|
||||
#[===============================[
|
||||
test sources:
|
||||
subdir: jtx
|
||||
|
||||
@@ -130,6 +130,7 @@ test.csf > ripple.json
|
||||
test.csf > ripple.protocol
|
||||
test.json > ripple.beast
|
||||
test.json > ripple.json
|
||||
test.json > ripple.rpc
|
||||
test.json > test.jtx
|
||||
test.jtx > ripple.app
|
||||
test.jtx > ripple.basics
|
||||
|
||||
@@ -39,7 +39,7 @@ BookListeners::removeSubscriber(std::uint64_t seq)
|
||||
|
||||
void
|
||||
BookListeners::publish(
|
||||
Json::Value const& jvObj,
|
||||
MultiApiJson const& jvObj,
|
||||
hash_set<std::uint64_t>& havePublished)
|
||||
{
|
||||
std::lock_guard sl(mLock);
|
||||
@@ -54,7 +54,8 @@ BookListeners::publish(
|
||||
// Only publish jvObj if this is the first occurence
|
||||
if (havePublished.emplace(p->getSeq()).second)
|
||||
{
|
||||
p->send(jvObj, true);
|
||||
p->send(
|
||||
jvObj.select(apiVersionSelector(p->getApiVersion())), true);
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
#ifndef RIPPLE_APP_LEDGER_BOOKLISTENERS_H_INCLUDED
|
||||
#define RIPPLE_APP_LEDGER_BOOKLISTENERS_H_INCLUDED
|
||||
|
||||
#include <ripple/json/MultivarJson.h>
|
||||
#include <ripple/net/InfoSub.h>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
@@ -58,7 +60,7 @@ public:
|
||||
|
||||
*/
|
||||
void
|
||||
publish(Json::Value const& jvObj, hash_set<std::uint64_t>& havePublished);
|
||||
publish(MultiApiJson const& jvObj, hash_set<std::uint64_t>& havePublished);
|
||||
|
||||
private:
|
||||
std::recursive_mutex mLock;
|
||||
|
||||
@@ -250,7 +250,7 @@ void
|
||||
OrderBookDB::processTxn(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
const AcceptedLedgerTx& alTx,
|
||||
Json::Value const& jvObj)
|
||||
MultiApiJson const& jvObj)
|
||||
{
|
||||
std::lock_guard sl(mLock);
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <ripple/app/ledger/AcceptedLedgerTx.h>
|
||||
#include <ripple/app/ledger/BookListeners.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/json/MultivarJson.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
@@ -63,7 +65,7 @@ public:
|
||||
processTxn(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
const AcceptedLedgerTx& alTx,
|
||||
Json::Value const& jvObj);
|
||||
MultiApiJson const& jvObj);
|
||||
|
||||
private:
|
||||
Application& app_;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <ripple/app/ledger/LedgerToJson.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/DeliverMax.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/basics/base_uint.h>
|
||||
#include <ripple/core/Pg.h>
|
||||
@@ -123,6 +124,7 @@ fillJsonTx(
|
||||
else
|
||||
{
|
||||
copyFrom(txJson, txn->getJson(JsonOptions::none));
|
||||
RPC::insertDeliverMax(txJson, txnType, fill.context->apiVersion);
|
||||
if (stMeta)
|
||||
{
|
||||
txJson[jss::metaData] = stMeta->getJson(JsonOptions::none);
|
||||
|
||||
53
src/ripple/app/misc/DeliverMax.h
Normal file
53
src/ripple/app/misc/DeliverMax.h
Normal file
@@ -0,0 +1,53 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 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_APP_MISC_DELIVERMAX_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_DELIVERMAX_H_INCLUDED
|
||||
|
||||
#include <ripple/protocol/TxFormats.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace Json {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace RPC {
|
||||
|
||||
/**
|
||||
Copy `Amount` field to `DeliverMax` field in transaction output JSON.
|
||||
This only applies to Payment transaction type, all others are ignored.
|
||||
|
||||
When apiVersion > 1 will also remove `Amount` field, forcing users
|
||||
to access this value using new `DeliverMax` field only.
|
||||
@{
|
||||
*/
|
||||
|
||||
void
|
||||
insertDeliverMax(Json::Value& tx_json, TxType txnType, unsigned int apiVersion);
|
||||
|
||||
/** @} */
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <ripple/app/ledger/TransactionMaster.h>
|
||||
#include <ripple/app/main/LoadManager.h>
|
||||
#include <ripple/app/misc/AmendmentTable.h>
|
||||
#include <ripple/app/misc/DeliverMax.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/LoadFeeTrack.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
@@ -53,6 +54,7 @@
|
||||
#include <ripple/consensus/ConsensusParms.h>
|
||||
#include <ripple/crypto/RFC1751.h>
|
||||
#include <ripple/crypto/csprng.h>
|
||||
#include <ripple/json/MultivarJson.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/net/RPCErr.h>
|
||||
#include <ripple/nodestore/DatabaseShard.h>
|
||||
@@ -551,12 +553,13 @@ private:
|
||||
void
|
||||
processClusterTimer();
|
||||
|
||||
Json::Value
|
||||
MultiApiJson
|
||||
transJson(
|
||||
const STTx& transaction,
|
||||
std::shared_ptr<STTx const> const& transaction,
|
||||
TER result,
|
||||
bool validated,
|
||||
std::shared_ptr<ReadView const> const& ledger);
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
std::optional<std::reference_wrapper<TxMeta const>> meta);
|
||||
|
||||
void
|
||||
pubValidatedTransaction(
|
||||
@@ -2785,7 +2788,8 @@ NetworkOPsImp::pubProposedTransaction(
|
||||
if (hook::isEmittedTxn(*transaction))
|
||||
return;
|
||||
|
||||
Json::Value jvObj = transJson(*transaction, result, false, ledger);
|
||||
MultiApiJson jvObj =
|
||||
transJson(transaction, result, false, ledger, std::nullopt);
|
||||
|
||||
{
|
||||
std::lock_guard sl(mSubLock);
|
||||
@@ -2797,7 +2801,8 @@ NetworkOPsImp::pubProposedTransaction(
|
||||
|
||||
if (p)
|
||||
{
|
||||
p->send(jvObj, true);
|
||||
p->send(
|
||||
jvObj.select(apiVersionSelector(p->getApiVersion())), true);
|
||||
++it;
|
||||
}
|
||||
else
|
||||
@@ -3122,12 +3127,13 @@ NetworkOPsImp::getLocalTxCount()
|
||||
|
||||
// This routine should only be used to publish accepted or validated
|
||||
// transactions.
|
||||
Json::Value
|
||||
MultiApiJson
|
||||
NetworkOPsImp::transJson(
|
||||
const STTx& transaction,
|
||||
std::shared_ptr<STTx const> const& transaction,
|
||||
TER result,
|
||||
bool validated,
|
||||
std::shared_ptr<ReadView const> const& ledger)
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
std::optional<std::reference_wrapper<TxMeta const>> meta)
|
||||
{
|
||||
Json::Value jvObj(Json::objectValue);
|
||||
std::string sToken;
|
||||
@@ -3136,16 +3142,23 @@ NetworkOPsImp::transJson(
|
||||
transResultInfo(result, sToken, sHuman);
|
||||
|
||||
jvObj[jss::type] = "transaction";
|
||||
jvObj[jss::transaction] = transaction.getJson(JsonOptions::none);
|
||||
jvObj[jss::transaction] = transaction->getJson(JsonOptions::none);
|
||||
|
||||
if (meta)
|
||||
{
|
||||
jvObj[jss::meta] = meta->get().getJson(JsonOptions::none);
|
||||
RPC::insertDeliveredAmount(
|
||||
jvObj[jss::meta], *ledger, transaction, meta->get());
|
||||
}
|
||||
|
||||
// add CTID where the needed data for it exists
|
||||
if (auto const& lookup = ledger->txRead(transaction.getTransactionID());
|
||||
if (auto const& lookup = ledger->txRead(transaction->getTransactionID());
|
||||
lookup.second && lookup.second->isFieldPresent(sfTransactionIndex))
|
||||
{
|
||||
uint32_t txnSeq = lookup.second->getFieldU32(sfTransactionIndex);
|
||||
uint32_t netID = app_.config().NETWORK_ID;
|
||||
if (transaction.isFieldPresent(sfNetworkID))
|
||||
netID = transaction.getFieldU32(sfNetworkID);
|
||||
if (transaction->isFieldPresent(sfNetworkID))
|
||||
netID = transaction->getFieldU32(sfNetworkID);
|
||||
|
||||
if (txnSeq <= 0xFFFFU && netID < 0xFFFFU &&
|
||||
ledger->info().seq < 0xFFFFFFFUL)
|
||||
@@ -3178,10 +3191,10 @@ NetworkOPsImp::transJson(
|
||||
jvObj[jss::engine_result_code] = result;
|
||||
jvObj[jss::engine_result_message] = sHuman;
|
||||
|
||||
if (transaction.getTxnType() == ttOFFER_CREATE)
|
||||
if (transaction->getTxnType() == ttOFFER_CREATE)
|
||||
{
|
||||
auto const account = transaction.getAccountID(sfAccount);
|
||||
auto const amount = transaction.getFieldAmount(sfTakerGets);
|
||||
auto const account = transaction->getAccountID(sfAccount);
|
||||
auto const amount = transaction->getFieldAmount(sfTakerGets);
|
||||
|
||||
// If the offer create is not self funded then add the owner balance
|
||||
if (account != amount.issue().account)
|
||||
@@ -3196,7 +3209,31 @@ NetworkOPsImp::transJson(
|
||||
}
|
||||
}
|
||||
|
||||
return jvObj;
|
||||
MultiApiJson multiObj({jvObj, jvObj});
|
||||
// Minimum supported API version must match index 0 in MultiApiJson
|
||||
static_assert(apiVersionSelector(RPC::apiMinimumSupportedVersion)() == 0);
|
||||
// Beta API version must match last index in MultiApiJson
|
||||
static_assert(
|
||||
apiVersionSelector(RPC::apiBetaVersion)() + 1 //
|
||||
== MultiApiJson::size);
|
||||
for (unsigned apiVersion = RPC::apiMinimumSupportedVersion,
|
||||
lastIndex = MultiApiJson::size;
|
||||
apiVersion <= RPC::apiBetaVersion;
|
||||
++apiVersion)
|
||||
{
|
||||
unsigned const index = apiVersionSelector(apiVersion)();
|
||||
assert(index < MultiApiJson::size);
|
||||
if (index != lastIndex)
|
||||
{
|
||||
RPC::insertDeliverMax(
|
||||
multiObj.val[index][jss::transaction],
|
||||
transaction->getTxnType(),
|
||||
apiVersion);
|
||||
lastIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
return multiObj;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -3207,14 +3244,10 @@ NetworkOPsImp::pubValidatedTransaction(
|
||||
{
|
||||
auto const& stTxn = transaction.getTxn();
|
||||
|
||||
Json::Value jvObj =
|
||||
transJson(*stTxn, transaction.getResult(), true, ledger);
|
||||
|
||||
{
|
||||
auto const& meta = transaction.getMeta();
|
||||
jvObj[jss::meta] = meta.getJson(JsonOptions::none);
|
||||
RPC::insertDeliveredAmount(jvObj[jss::meta], *ledger, stTxn, meta);
|
||||
}
|
||||
// Create two different Json objects, for different API versions
|
||||
auto const metaRef = std::ref(transaction.getMeta());
|
||||
auto const trResult = transaction.getResult();
|
||||
MultiApiJson jvObj = transJson(stTxn, trResult, true, ledger, metaRef);
|
||||
|
||||
{
|
||||
std::lock_guard sl(mSubLock);
|
||||
@@ -3226,7 +3259,8 @@ NetworkOPsImp::pubValidatedTransaction(
|
||||
|
||||
if (p)
|
||||
{
|
||||
p->send(jvObj, true);
|
||||
p->send(
|
||||
jvObj.select(apiVersionSelector(p->getApiVersion())), true);
|
||||
++it;
|
||||
}
|
||||
else
|
||||
@@ -3241,7 +3275,8 @@ NetworkOPsImp::pubValidatedTransaction(
|
||||
|
||||
if (p)
|
||||
{
|
||||
p->send(jvObj, true);
|
||||
p->send(
|
||||
jvObj.select(apiVersionSelector(p->getApiVersion())), true);
|
||||
++it;
|
||||
}
|
||||
else
|
||||
@@ -3354,30 +3389,35 @@ NetworkOPsImp::pubAccountTransaction(
|
||||
{
|
||||
auto const& stTxn = transaction.getTxn();
|
||||
|
||||
Json::Value jvObj =
|
||||
transJson(*stTxn, transaction.getResult(), true, ledger);
|
||||
|
||||
{
|
||||
auto const& meta = transaction.getMeta();
|
||||
|
||||
jvObj[jss::meta] = meta.getJson(JsonOptions::none);
|
||||
RPC::insertDeliveredAmount(jvObj[jss::meta], *ledger, stTxn, meta);
|
||||
}
|
||||
// Create two different Json objects, for different API versions
|
||||
auto const metaRef = std::ref(transaction.getMeta());
|
||||
auto const trResult = transaction.getResult();
|
||||
MultiApiJson jvObj = transJson(stTxn, trResult, true, ledger, metaRef);
|
||||
|
||||
for (InfoSub::ref isrListener : notify)
|
||||
isrListener->send(jvObj, true);
|
||||
{
|
||||
isrListener->send(
|
||||
jvObj.select(apiVersionSelector(isrListener->getApiVersion())),
|
||||
true);
|
||||
}
|
||||
|
||||
if (last)
|
||||
jvObj[jss::account_history_boundary] = true;
|
||||
jvObj.set(jss::account_history_boundary, true);
|
||||
|
||||
assert(!jvObj.isMember(jss::account_history_tx_stream));
|
||||
assert(
|
||||
jvObj.isMember(jss::account_history_tx_stream) ==
|
||||
MultiApiJson::none);
|
||||
for (auto& info : accountHistoryNotify)
|
||||
{
|
||||
auto& index = info.index_;
|
||||
if (index->forwardTxIndex_ == 0 && !index->haveHistorical_)
|
||||
jvObj[jss::account_history_tx_first] = true;
|
||||
jvObj[jss::account_history_tx_index] = index->forwardTxIndex_++;
|
||||
info.sink_->send(jvObj, true);
|
||||
jvObj.set(jss::account_history_tx_first, true);
|
||||
|
||||
jvObj.set(jss::account_history_tx_index, index->forwardTxIndex_++);
|
||||
|
||||
info.sink_->send(
|
||||
jvObj.select(apiVersionSelector(info.sink_->getApiVersion())),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3431,19 +3471,26 @@ NetworkOPsImp::pubProposedAccountTransaction(
|
||||
|
||||
if (!notify.empty() || !accountHistoryNotify.empty())
|
||||
{
|
||||
Json::Value jvObj = transJson(*tx, result, false, ledger);
|
||||
// Create two different Json objects, for different API versions
|
||||
MultiApiJson jvObj = transJson(tx, result, false, ledger, std::nullopt);
|
||||
|
||||
for (InfoSub::ref isrListener : notify)
|
||||
isrListener->send(jvObj, true);
|
||||
isrListener->send(
|
||||
jvObj.select(apiVersionSelector(isrListener->getApiVersion())),
|
||||
true);
|
||||
|
||||
assert(!jvObj.isMember(jss::account_history_tx_stream));
|
||||
assert(
|
||||
jvObj.isMember(jss::account_history_tx_stream) ==
|
||||
MultiApiJson::none);
|
||||
for (auto& info : accountHistoryNotify)
|
||||
{
|
||||
auto& index = info.index_;
|
||||
if (index->forwardTxIndex_ == 0 && !index->haveHistorical_)
|
||||
jvObj[jss::account_history_tx_first] = true;
|
||||
jvObj[jss::account_history_tx_index] = index->forwardTxIndex_++;
|
||||
info.sink_->send(jvObj, true);
|
||||
jvObj.set(jss::account_history_tx_first, true);
|
||||
jvObj.set(jss::account_history_tx_index, index->forwardTxIndex_++);
|
||||
info.sink_->send(
|
||||
jvObj.select(apiVersionSelector(info.sink_->getApiVersion())),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3634,7 +3681,7 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo)
|
||||
|
||||
auto send = [&](Json::Value const& jvObj,
|
||||
bool unsubscribe) -> bool {
|
||||
if (auto sptr = subInfo.sinkWptr_.lock(); sptr)
|
||||
if (auto sptr = subInfo.sinkWptr_.lock())
|
||||
{
|
||||
sptr->send(jvObj, true);
|
||||
if (unsubscribe)
|
||||
@@ -3645,6 +3692,22 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo)
|
||||
return false;
|
||||
};
|
||||
|
||||
auto sendMultiApiJson = [&](MultiApiJson const& jvObj,
|
||||
bool unsubscribe) -> bool {
|
||||
if (auto sptr = subInfo.sinkWptr_.lock())
|
||||
{
|
||||
sptr->send(
|
||||
jvObj.select(apiVersionSelector(sptr->getApiVersion())),
|
||||
true);
|
||||
|
||||
if (unsubscribe)
|
||||
unsubAccountHistory(sptr, accountId, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
auto getMoreTxns =
|
||||
[&](std::uint32_t minLedger,
|
||||
std::uint32_t maxLedger,
|
||||
@@ -3803,21 +3866,22 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo)
|
||||
send(rpcError(rpcINTERNAL), true);
|
||||
return;
|
||||
}
|
||||
Json::Value jvTx = transJson(
|
||||
*stTxn, meta->getResultTER(), true, curTxLedger);
|
||||
jvTx[jss::meta] = meta->getJson(JsonOptions::none);
|
||||
jvTx[jss::account_history_tx_index] = txHistoryIndex--;
|
||||
|
||||
auto const mRef = std::ref(*meta);
|
||||
auto const trR = meta->getResultTER();
|
||||
MultiApiJson jvTx =
|
||||
transJson(stTxn, trR, true, curTxLedger, mRef);
|
||||
|
||||
jvTx.set(
|
||||
jss::account_history_tx_index, txHistoryIndex--);
|
||||
if (i + 1 == num_txns ||
|
||||
txns[i + 1].first->getLedger() != tx->getLedger())
|
||||
jvTx[jss::account_history_boundary] = true;
|
||||
jvTx.set(jss::account_history_boundary, true);
|
||||
|
||||
RPC::insertDeliveredAmount(
|
||||
jvTx[jss::meta], *curTxLedger, stTxn, *meta);
|
||||
if (isFirstTx(tx, meta))
|
||||
{
|
||||
jvTx[jss::account_history_tx_first] = true;
|
||||
send(jvTx, false);
|
||||
jvTx.set(jss::account_history_tx_first, true);
|
||||
sendMultiApiJson(jvTx, false);
|
||||
|
||||
JLOG(m_journal.trace())
|
||||
<< "AccountHistory job for account "
|
||||
@@ -3827,7 +3891,7 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo)
|
||||
}
|
||||
else
|
||||
{
|
||||
send(jvTx, false);
|
||||
sendMultiApiJson(jvTx, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
42
src/ripple/app/misc/impl/DeliverMax.cpp
Normal file
42
src/ripple/app/misc/impl/DeliverMax.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 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/app/misc/DeliverMax.h>
|
||||
|
||||
#include <ripple/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace RPC {
|
||||
|
||||
void
|
||||
insertDeliverMax(Json::Value& tx_json, TxType txnType, unsigned int apiVersion)
|
||||
{
|
||||
if (tx_json.isMember(jss::Amount))
|
||||
{
|
||||
if (txnType == ttPAYMENT)
|
||||
{
|
||||
tx_json[jss::DeliverMax] = tx_json[jss::Amount];
|
||||
if (apiVersion > 1)
|
||||
tx_json.removeMember(jss::Amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
112
src/ripple/json/MultivarJson.h
Normal file
112
src/ripple/json/MultivarJson.h
Normal file
@@ -0,0 +1,112 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 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_JSON_MULTIVARJSON_H_INCLUDED
|
||||
#define RIPPLE_JSON_MULTIVARJSON_H_INCLUDED
|
||||
|
||||
#include <ripple/json/json_value.h>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <concepts>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace ripple {
|
||||
template <std::size_t Size>
|
||||
struct MultivarJson
|
||||
{
|
||||
std::array<Json::Value, Size> val;
|
||||
constexpr static std::size_t size = Size;
|
||||
|
||||
Json::Value const&
|
||||
select(auto&& selector) const
|
||||
requires std::same_as<std::size_t, decltype(selector())>
|
||||
{
|
||||
auto const index = selector();
|
||||
assert(index < size);
|
||||
return val[index];
|
||||
}
|
||||
|
||||
void
|
||||
set(const char* key,
|
||||
auto const&
|
||||
v) requires std::constructible_from<Json::Value, decltype(v)>
|
||||
{
|
||||
for (auto& a : this->val)
|
||||
a[key] = v;
|
||||
}
|
||||
|
||||
// Intentionally not using class enum here, MultivarJson is scope enough
|
||||
enum IsMemberResult : int { none = 0, some, all };
|
||||
|
||||
[[nodiscard]] IsMemberResult
|
||||
isMember(const char* key) const
|
||||
{
|
||||
int count = 0;
|
||||
for (auto& a : this->val)
|
||||
if (a.isMember(key))
|
||||
count += 1;
|
||||
|
||||
return (count == 0 ? none : (count < size ? some : all));
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for Json for all supported API versions.
|
||||
using MultiApiJson = MultivarJson<2>;
|
||||
|
||||
/*
|
||||
|
||||
NOTE:
|
||||
|
||||
If a future API version change adds another possible format, change the size of
|
||||
`MultiApiJson`, and update `apiVersionSelector()` to return the appropriate
|
||||
selection value for the new `apiVersion` and higher.
|
||||
|
||||
e.g. There are 2 formats now, the first, for version one, the second for
|
||||
versions > 1. Hypothetically, if API version 4 adds a new format, `MultiApiJson`
|
||||
would be MultivarJson<3>, and `apiVersionSelector` would return
|
||||
`static_cast<std::size_t>(apiVersion < 2 ? 0u : (apiVersion < 4 ? 1u : 2u))`
|
||||
|
||||
NOTE:
|
||||
|
||||
The more different JSON formats we support, the more CPU cycles we need to
|
||||
pre-build JSON for different API versions e.g. when publishing streams to
|
||||
`subscribe` clients. Hence it is desirable to keep MultiApiJson small and
|
||||
instead fully deprecate and remove support for old API versions. For example, if
|
||||
we removed support for API version 1 and added a different format for API
|
||||
version 3, the `apiVersionSelector` would change to
|
||||
`static_cast<std::size_t>(apiVersion > 2)`
|
||||
|
||||
*/
|
||||
|
||||
// Helper to create appropriate selector for indexing MultiApiJson by apiVersion
|
||||
constexpr auto
|
||||
apiVersionSelector(unsigned int apiVersion) noexcept
|
||||
{
|
||||
return [apiVersion]() constexpr
|
||||
{
|
||||
// apiVersion <= 1 returns 0
|
||||
// apiVersion > 1 returns 1
|
||||
return static_cast<std::size_t>(apiVersion > 1);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -229,6 +229,12 @@ public:
|
||||
std::shared_ptr<InfoSubRequest> const&
|
||||
getRequest();
|
||||
|
||||
void
|
||||
setApiVersion(unsigned int apiVersion);
|
||||
|
||||
unsigned int
|
||||
getApiVersion() const noexcept;
|
||||
|
||||
protected:
|
||||
std::mutex mLock;
|
||||
|
||||
@@ -240,6 +246,7 @@ private:
|
||||
std::shared_ptr<InfoSubRequest> request_;
|
||||
std::uint64_t mSeq;
|
||||
hash_set<AccountID> accountHistorySubscriptions_;
|
||||
unsigned int apiVersion_ = 0;
|
||||
|
||||
static int
|
||||
assign_id()
|
||||
|
||||
@@ -136,4 +136,17 @@ InfoSub::getRequest()
|
||||
return request_;
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::setApiVersion(unsigned int apiVersion)
|
||||
{
|
||||
apiVersion_ = apiVersion;
|
||||
}
|
||||
|
||||
unsigned int
|
||||
InfoSub::getApiVersion() const noexcept
|
||||
{
|
||||
assert(apiVersion_ > 0);
|
||||
return apiVersion_;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -76,6 +76,7 @@ JSS(CreateCode); // field.
|
||||
JSS(DID); // ledger type.
|
||||
JSS(DIDDelete); // transaction type.
|
||||
JSS(DIDSet); // transaction type.
|
||||
JSS(DeliverMax); // out: alias to Amount
|
||||
JSS(DeliverMin); // in: TransactionSign
|
||||
JSS(DepositPreauth); // transaction and ledger type.
|
||||
JSS(Destination); // in: TransactionSign; field.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <ripple/app/ledger/LedgerMaster.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/DeliverMax.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
#include <ripple/app/rdb/backend/PostgresDatabase.h>
|
||||
@@ -326,6 +327,9 @@ populateJsonResponse(
|
||||
Json::Value& jvObj = jvTxns.append(Json::objectValue);
|
||||
|
||||
jvObj[jss::tx] = txn->getJson(JsonOptions::include_date);
|
||||
auto const& sttx = txn->getSTransaction();
|
||||
RPC::insertDeliverMax(
|
||||
jvObj[jss::tx], sttx->getTxnType(), context.apiVersion);
|
||||
if (txnMeta)
|
||||
{
|
||||
jvObj[jss::meta] =
|
||||
@@ -333,8 +337,7 @@ populateJsonResponse(
|
||||
jvObj[jss::validated] = true;
|
||||
insertDeliveredAmount(
|
||||
jvObj[jss::meta], context, txn, *txnMeta);
|
||||
insertNFTSyntheticInJson(
|
||||
jvObj, txn->getSTransaction(), *txnMeta);
|
||||
insertNFTSyntheticInJson(jvObj, sttx, *txnMeta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ doPathFind(RPC::JsonContext& context)
|
||||
if (!context.infoSub)
|
||||
return rpcError(rpcNO_EVENTS);
|
||||
|
||||
context.infoSub->setApiVersion(context.apiVersion);
|
||||
|
||||
auto sSubCommand = context.params[jss::subcommand].asString();
|
||||
|
||||
if (sSubCommand == "create")
|
||||
|
||||
@@ -111,6 +111,7 @@ doSubscribe(RPC::JsonContext& context)
|
||||
{
|
||||
ispSub = context.infoSub;
|
||||
}
|
||||
ispSub->setApiVersion(context.apiVersion);
|
||||
|
||||
if (context.params.isMember(jss::streams))
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/DeliverMax.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
@@ -71,6 +72,8 @@ doTransactionEntry(RPC::JsonContext& context)
|
||||
else
|
||||
{
|
||||
jvResult[jss::tx_json] = sttx->getJson(JsonOptions::none);
|
||||
RPC::insertDeliverMax(
|
||||
jvResult[jss::tx_json], sttx->getTxnType(), context.apiVersion);
|
||||
if (stobj)
|
||||
jvResult[jss::metadata] = stobj->getJson(JsonOptions::none);
|
||||
// 'accounts'
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <ripple/app/ledger/LedgerMaster.h>
|
||||
#include <ripple/app/ledger/TransactionMaster.h>
|
||||
#include <ripple/app/misc/DeliverMax.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
#include <ripple/basics/ToString.h>
|
||||
@@ -318,6 +319,10 @@ populateJsonResponse(
|
||||
else if (result.txn)
|
||||
{
|
||||
response = result.txn->getJson(JsonOptions::include_date, args.binary);
|
||||
auto const& sttx = result.txn->getSTransaction();
|
||||
if (!args.binary)
|
||||
RPC::insertDeliverMax(
|
||||
response, sttx->getTxnType(), context.apiVersion);
|
||||
|
||||
// populate binary metadata
|
||||
if (auto blob = std::get_if<Blob>(&result.meta))
|
||||
@@ -334,8 +339,7 @@ populateJsonResponse(
|
||||
response[jss::meta] = meta->getJson(JsonOptions::none);
|
||||
insertDeliveredAmount(
|
||||
response[jss::meta], context, result.txn, *meta);
|
||||
insertNFTSyntheticInJson(
|
||||
response, result.txn->getSTransaction(), *meta);
|
||||
insertNFTSyntheticInJson(response, sttx, *meta);
|
||||
}
|
||||
}
|
||||
response[jss::validated] = result.validated;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <ripple/app/ledger/LedgerMaster.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/DeliverMax.h>
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
#include <ripple/app/rdb/RelationalDatabase.h>
|
||||
#include <ripple/core/DatabaseCon.h>
|
||||
@@ -63,7 +64,12 @@ doTxHistory(RPC::JsonContext& context)
|
||||
obj["used_postgres"] = true;
|
||||
|
||||
for (auto const& t : trans)
|
||||
txs.append(t->getJson(JsonOptions::none));
|
||||
{
|
||||
Json::Value tx_json = t->getJson(JsonOptions::none);
|
||||
RPC::insertDeliverMax(
|
||||
tx_json, t->getSTransaction()->getTxnType(), context.apiVersion);
|
||||
txs.append(tx_json);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -167,6 +167,22 @@ checkPayment(
|
||||
if (tx_json[jss::TransactionType].asString() != jss::Payment)
|
||||
return Json::Value();
|
||||
|
||||
// DeliverMax is an alias to Amount and we use Amount internally
|
||||
if (tx_json.isMember(jss::DeliverMax))
|
||||
{
|
||||
if (tx_json.isMember(jss::Amount))
|
||||
{
|
||||
if (tx_json[jss::DeliverMax] != tx_json[jss::Amount])
|
||||
return RPC::make_error(
|
||||
rpcINVALID_PARAMS,
|
||||
"Cannot specify differing 'Amount' and 'DeliverMax'");
|
||||
}
|
||||
else
|
||||
tx_json[jss::Amount] = tx_json[jss::DeliverMax];
|
||||
|
||||
tx_json.removeMember(jss::DeliverMax);
|
||||
}
|
||||
|
||||
if (!tx_json.isMember(jss::Amount))
|
||||
return RPC::missing_field_error("tx_json.Amount");
|
||||
|
||||
|
||||
293
src/test/json/MultivarJson_test.cpp
Normal file
293
src/test/json/MultivarJson_test.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/XRPLF/rippled/
|
||||
Copyright (c) 2023 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/json/MultivarJson.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include "ripple/beast/unit_test/suite.hpp"
|
||||
#include "ripple/json/json_value.h"
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
struct MultivarJson_test : beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
constexpr static Json::StaticString string1("string1");
|
||||
static Json::Value const str1{string1};
|
||||
|
||||
static Json::Value const obj1{[]() {
|
||||
Json::Value obj1(Json::objectValue);
|
||||
obj1["one"] = 1;
|
||||
return obj1;
|
||||
}()};
|
||||
|
||||
static Json::Value const jsonNull{};
|
||||
|
||||
MultivarJson<3> const subject({str1, obj1});
|
||||
static_assert(sizeof(subject) == sizeof(subject.val));
|
||||
static_assert(subject.size == subject.val.size());
|
||||
static_assert(
|
||||
std::is_same_v<decltype(subject.val), std::array<Json::Value, 3>>);
|
||||
|
||||
BEAST_EXPECT(subject.val.size() == 3);
|
||||
BEAST_EXPECT(
|
||||
(subject.val == std::array<Json::Value, 3>{str1, obj1, jsonNull}));
|
||||
BEAST_EXPECT(
|
||||
(MultivarJson<3>({obj1, str1}).val ==
|
||||
std::array<Json::Value, 3>{obj1, str1, jsonNull}));
|
||||
BEAST_EXPECT(
|
||||
(MultivarJson<3>({jsonNull, obj1, str1}).val ==
|
||||
std::array<Json::Value, 3>{jsonNull, obj1, str1}));
|
||||
|
||||
{
|
||||
testcase("default copy construction / assignment");
|
||||
|
||||
MultivarJson<3> x{subject};
|
||||
|
||||
BEAST_EXPECT(x.val.size() == subject.val.size());
|
||||
BEAST_EXPECT(x.val[0] == subject.val[0]);
|
||||
BEAST_EXPECT(x.val[1] == subject.val[1]);
|
||||
BEAST_EXPECT(x.val[2] == subject.val[2]);
|
||||
BEAST_EXPECT(x.val == subject.val);
|
||||
BEAST_EXPECT(&x.val[0] != &subject.val[0]);
|
||||
BEAST_EXPECT(&x.val[1] != &subject.val[1]);
|
||||
BEAST_EXPECT(&x.val[2] != &subject.val[2]);
|
||||
|
||||
MultivarJson<3> y;
|
||||
BEAST_EXPECT((y.val == std::array<Json::Value, 3>{}));
|
||||
y = subject;
|
||||
BEAST_EXPECT(y.val == subject.val);
|
||||
BEAST_EXPECT(&y.val[0] != &subject.val[0]);
|
||||
BEAST_EXPECT(&y.val[1] != &subject.val[1]);
|
||||
BEAST_EXPECT(&y.val[2] != &subject.val[2]);
|
||||
|
||||
y = std::move(x);
|
||||
BEAST_EXPECT(y.val == subject.val);
|
||||
BEAST_EXPECT(&y.val[0] != &subject.val[0]);
|
||||
BEAST_EXPECT(&y.val[1] != &subject.val[1]);
|
||||
BEAST_EXPECT(&y.val[2] != &subject.val[2]);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("select");
|
||||
|
||||
BEAST_EXPECT(
|
||||
subject.select([]() -> std::size_t { return 0; }) == str1);
|
||||
BEAST_EXPECT(
|
||||
subject.select([]() -> std::size_t { return 1; }) == obj1);
|
||||
BEAST_EXPECT(
|
||||
subject.select([]() -> std::size_t { return 2; }) == jsonNull);
|
||||
|
||||
// Tests of requires clause - these are expected to match
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.select([]() -> std::size_t { return 0; });
|
||||
};
|
||||
}(subject));
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.select([]() constexpr->std::size_t { return 0; });
|
||||
};
|
||||
}(subject));
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.select([]() mutable -> std::size_t { return 0; });
|
||||
};
|
||||
}(subject));
|
||||
|
||||
// Tests of requires clause - these are expected NOT to match
|
||||
static_assert([](auto&& a) {
|
||||
return !requires
|
||||
{
|
||||
subject.select([]() -> int { return 0; });
|
||||
};
|
||||
}(subject));
|
||||
static_assert([](auto&& v) {
|
||||
return !requires
|
||||
{
|
||||
v.select([]() -> void {});
|
||||
};
|
||||
}(subject));
|
||||
static_assert([](auto&& v) {
|
||||
return !requires
|
||||
{
|
||||
v.select([]() -> bool { return false; });
|
||||
};
|
||||
}(subject));
|
||||
}
|
||||
|
||||
{
|
||||
struct foo_t final
|
||||
{
|
||||
};
|
||||
testcase("set");
|
||||
|
||||
auto x = MultivarJson<2>{{Json::objectValue, Json::objectValue}};
|
||||
x.set("name1", 42);
|
||||
BEAST_EXPECT(x.val[0].isMember("name1"));
|
||||
BEAST_EXPECT(x.val[1].isMember("name1"));
|
||||
BEAST_EXPECT(x.val[0]["name1"].isInt());
|
||||
BEAST_EXPECT(x.val[1]["name1"].isInt());
|
||||
BEAST_EXPECT(x.val[0]["name1"].asInt() == 42);
|
||||
BEAST_EXPECT(x.val[1]["name1"].asInt() == 42);
|
||||
|
||||
x.set("name2", "bar");
|
||||
BEAST_EXPECT(x.val[0].isMember("name2"));
|
||||
BEAST_EXPECT(x.val[1].isMember("name2"));
|
||||
BEAST_EXPECT(x.val[0]["name2"].isString());
|
||||
BEAST_EXPECT(x.val[1]["name2"].isString());
|
||||
BEAST_EXPECT(x.val[0]["name2"].asString() == "bar");
|
||||
BEAST_EXPECT(x.val[1]["name2"].asString() == "bar");
|
||||
|
||||
// Tests of requires clause - these are expected to match
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.set("name", Json::nullValue);
|
||||
};
|
||||
}(x));
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.set("name", "value");
|
||||
};
|
||||
}(x));
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.set("name", true);
|
||||
};
|
||||
}(x));
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.set("name", 42);
|
||||
};
|
||||
}(x));
|
||||
|
||||
// Tests of requires clause - these are expected NOT to match
|
||||
static_assert([](auto&& v) {
|
||||
return !requires
|
||||
{
|
||||
v.set("name", foo_t{});
|
||||
};
|
||||
}(x));
|
||||
static_assert([](auto&& v) {
|
||||
return !requires
|
||||
{
|
||||
v.set("name", std::nullopt);
|
||||
};
|
||||
}(x));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("isMember");
|
||||
|
||||
// Well defined behaviour even if we have different types of members
|
||||
BEAST_EXPECT(subject.isMember("foo") == decltype(subject)::none);
|
||||
|
||||
auto const makeJson = [](const char* key, int val) {
|
||||
Json::Value obj1(Json::objectValue);
|
||||
obj1[key] = val;
|
||||
return obj1;
|
||||
};
|
||||
|
||||
{
|
||||
// All variants have element "One", none have element "Two"
|
||||
MultivarJson<2> const s1{
|
||||
{makeJson("One", 12), makeJson("One", 42)}};
|
||||
BEAST_EXPECT(s1.isMember("One") == decltype(s1)::all);
|
||||
BEAST_EXPECT(s1.isMember("Two") == decltype(s1)::none);
|
||||
}
|
||||
|
||||
{
|
||||
// Some variants have element "One" and some have "Two"
|
||||
MultivarJson<2> const s2{
|
||||
{makeJson("One", 12), makeJson("Two", 42)}};
|
||||
BEAST_EXPECT(s2.isMember("One") == decltype(s2)::some);
|
||||
BEAST_EXPECT(s2.isMember("Two") == decltype(s2)::some);
|
||||
}
|
||||
|
||||
{
|
||||
// Not all variants have element "One", because last one is null
|
||||
MultivarJson<3> const s3{
|
||||
{makeJson("One", 12), makeJson("One", 42), {}}};
|
||||
BEAST_EXPECT(s3.isMember("One") == decltype(s3)::some);
|
||||
BEAST_EXPECT(s3.isMember("Two") == decltype(s3)::none);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// NOTE It's fine to change this test when we change API versions
|
||||
testcase("apiVersionSelector");
|
||||
|
||||
static_assert(MultiApiJson::size == 2);
|
||||
static MultiApiJson x{{obj1, str1}};
|
||||
|
||||
static_assert(
|
||||
std::is_same_v<decltype(apiVersionSelector(1)()), std::size_t>);
|
||||
static_assert([](auto&& v) {
|
||||
return requires
|
||||
{
|
||||
v.select(apiVersionSelector(1));
|
||||
};
|
||||
}(x));
|
||||
|
||||
BEAST_EXPECT(x.select(apiVersionSelector(0)) == obj1);
|
||||
BEAST_EXPECT(x.select(apiVersionSelector(2)) == str1);
|
||||
|
||||
static_assert(apiVersionSelector(0)() == 0);
|
||||
static_assert(apiVersionSelector(1)() == 0);
|
||||
static_assert(apiVersionSelector(2)() == 1);
|
||||
static_assert(apiVersionSelector(3)() == 1);
|
||||
static_assert(
|
||||
apiVersionSelector(
|
||||
std::numeric_limits<unsigned int>::max())() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
// There should be no reson to change this test
|
||||
testcase("apiVersionSelector invariants");
|
||||
|
||||
static_assert(
|
||||
apiVersionSelector(RPC::apiMinimumSupportedVersion)() == 0);
|
||||
static_assert(
|
||||
apiVersionSelector(RPC::apiBetaVersion)() + 1 //
|
||||
== MultiApiJson::size);
|
||||
|
||||
BEAST_EXPECT(MultiApiJson::size >= 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(MultivarJson, ripple_basics, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -122,14 +122,23 @@ class AccountTx_test : public beast::unit_test::suite
|
||||
// Ledger 3 has the two txs associated with funding the account
|
||||
// All other ledgers have no txs
|
||||
|
||||
auto hasTxs = [](Json::Value const& j) {
|
||||
auto hasTxs = [apiVersion](Json::Value const& j) {
|
||||
return j.isMember(jss::result) &&
|
||||
(j[jss::result][jss::status] == "success") &&
|
||||
(j[jss::result][jss::transactions].size() == 2) &&
|
||||
(j[jss::result][jss::transactions][0u][jss::tx]
|
||||
[jss::TransactionType] == jss::AccountSet) &&
|
||||
(j[jss::result][jss::transactions][1u][jss::tx]
|
||||
[jss::TransactionType] == jss::Payment);
|
||||
[jss::TransactionType] == jss::Payment) &&
|
||||
(j[jss::result][jss::transactions][1u][jss::tx]
|
||||
[jss::DeliverMax] == "10000000010") &&
|
||||
((apiVersion > 1 &&
|
||||
!j[jss::result][jss::transactions][1u][jss::tx].isMember(
|
||||
jss::Amount)) ||
|
||||
(apiVersion <= 1 &&
|
||||
j[jss::result][jss::transactions][1u][jss::tx][jss::Amount] ==
|
||||
j[jss::result][jss::transactions][1u][jss::tx]
|
||||
[jss::DeliverMax]));
|
||||
};
|
||||
|
||||
auto noTxs = [](Json::Value const& j) {
|
||||
|
||||
@@ -1034,6 +1034,26 @@ static constexpr TxnTestData txnTestArray[] = {
|
||||
"Missing field 'tx_json.Destination'.",
|
||||
"Missing field 'tx_json.Destination'."}}},
|
||||
|
||||
{"Missing 'Destination' in sign_for, use DeliverMax",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "doesnt_matter",
|
||||
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"secret": "masterpassphrase",
|
||||
"tx_json": {
|
||||
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"DeliverMax": "1000000000",
|
||||
"Fee": 50,
|
||||
"Sequence": 0,
|
||||
"SigningPubKey": "",
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
})",
|
||||
{{"Missing field 'tx_json.Destination'.",
|
||||
"Missing field 'tx_json.Destination'.",
|
||||
"Missing field 'tx_json.Destination'.",
|
||||
"Missing field 'tx_json.Destination'."}}},
|
||||
|
||||
{"Missing 'Fee' in sign_for.",
|
||||
__LINE__,
|
||||
R"({
|
||||
@@ -1691,6 +1711,34 @@ static constexpr TxnTestData txnTestArray[] = {
|
||||
"Missing field 'account'.",
|
||||
"Invalid field 'tx_json.Amount'."}}},
|
||||
|
||||
{"Invalid DeliverMax in submit_multisigned Payment.",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "submit_multisigned",
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"DeliverMax": "NotANumber",
|
||||
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"Fee": 50,
|
||||
"Sequence": 0,
|
||||
"Signers": [
|
||||
{
|
||||
"Signer": {
|
||||
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
|
||||
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
|
||||
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
|
||||
}
|
||||
}
|
||||
],
|
||||
"SigningPubKey": "",
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
})",
|
||||
{{"Missing field 'secret'.",
|
||||
"Missing field 'secret'.",
|
||||
"Missing field 'account'.",
|
||||
"Invalid field 'tx_json.Amount'."}}},
|
||||
|
||||
{"No build_path in submit_multisigned.",
|
||||
__LINE__,
|
||||
R"({
|
||||
@@ -1904,6 +1952,72 @@ static constexpr TxnTestData txnTestArray[] = {
|
||||
"A Signer may not be the transaction's Account "
|
||||
"(rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh)."}}},
|
||||
|
||||
{"Empty Signers array in submit_multisigned, use DeliverMax",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "submit_multisigned",
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"DeliverMax": "10000000",
|
||||
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"Fee": 50,
|
||||
"Sequence": 0,
|
||||
"Signers": [
|
||||
],
|
||||
"SigningPubKey": "",
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
})",
|
||||
{{"Missing field 'secret'.",
|
||||
"Missing field 'secret'.",
|
||||
"Missing field 'account'.",
|
||||
"tx_json.Signers array may not be empty."}}},
|
||||
|
||||
{"Empty Signers array in submit_multisigned, use DeliverMax and Amount",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "submit_multisigned",
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Amount": "10000000",
|
||||
"DeliverMax": "10000000",
|
||||
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"Fee": 50,
|
||||
"Sequence": 0,
|
||||
"Signers": [
|
||||
],
|
||||
"SigningPubKey": "",
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
})",
|
||||
{{"Missing field 'secret'.",
|
||||
"Missing field 'secret'.",
|
||||
"Missing field 'account'.",
|
||||
"tx_json.Signers array may not be empty."}}},
|
||||
|
||||
{"Payment cannot specify different DeliverMax and Amount.",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "doesnt_matter",
|
||||
"account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"secret": "masterpassphrase",
|
||||
"debug_signing": 0,
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Amount": "1000000000",
|
||||
"DeliverMax": "1000000020",
|
||||
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"Fee": 50,
|
||||
"Sequence": 0,
|
||||
"SigningPubKey": "",
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
})",
|
||||
{{"Cannot specify differing 'Amount' and 'DeliverMax'",
|
||||
"Cannot specify differing 'Amount' and 'DeliverMax'",
|
||||
"Cannot specify differing 'Amount' and 'DeliverMax'",
|
||||
"Cannot specify differing 'Amount' and 'DeliverMax'"}}},
|
||||
|
||||
};
|
||||
|
||||
class JSONRPC_test : public beast::unit_test::suite
|
||||
|
||||
@@ -194,8 +194,16 @@ public:
|
||||
// Check stream update for payment transaction
|
||||
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
|
||||
return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
|
||||
["NewFields"][jss::Account] ==
|
||||
Account("alice").human();
|
||||
["NewFields"][jss::Account] //
|
||||
== Account("alice").human() &&
|
||||
jv[jss::transaction][jss::TransactionType] //
|
||||
== jss::Payment &&
|
||||
jv[jss::transaction][jss::DeliverMax] //
|
||||
== "10000000010" &&
|
||||
jv[jss::transaction][jss::Fee] //
|
||||
== "10" &&
|
||||
jv[jss::transaction][jss::Sequence] //
|
||||
== 1;
|
||||
}));
|
||||
|
||||
// Check stream update for accountset transaction
|
||||
@@ -211,7 +219,16 @@ public:
|
||||
// Check stream update for payment transaction
|
||||
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
|
||||
return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
|
||||
["NewFields"][jss::Account] == Account("bob").human();
|
||||
["NewFields"][jss::Account] //
|
||||
== Account("bob").human() &&
|
||||
jv[jss::transaction][jss::TransactionType] //
|
||||
== jss::Payment &&
|
||||
jv[jss::transaction][jss::DeliverMax] //
|
||||
== "10000000010" &&
|
||||
jv[jss::transaction][jss::Fee] //
|
||||
== "10" &&
|
||||
jv[jss::transaction][jss::Sequence] //
|
||||
== 2;
|
||||
}));
|
||||
|
||||
// Check stream update for accountset transaction
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/json/json_reader.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/Env.h>
|
||||
@@ -145,12 +147,12 @@ class TransactionEntry_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Basic request");
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
Env env{*this, supported_amendments() - featureXahauGenesis};
|
||||
|
||||
auto check_tx = [this, &env](
|
||||
int index,
|
||||
std::string const txhash,
|
||||
std::string const type = "") {
|
||||
std::string const expected_json = "") {
|
||||
// first request using ledger_index to lookup
|
||||
Json::Value const resIndex{[&env, index, &txhash]() {
|
||||
Json::Value params{Json::objectValue};
|
||||
@@ -164,13 +166,30 @@ class TransactionEntry_test : public beast::unit_test::suite
|
||||
return;
|
||||
|
||||
BEAST_EXPECT(resIndex[jss::tx_json][jss::hash] == txhash);
|
||||
if (!type.empty())
|
||||
if (!expected_json.empty())
|
||||
{
|
||||
BEAST_EXPECTS(
|
||||
resIndex[jss::tx_json][jss::TransactionType] == type,
|
||||
txhash + " is " +
|
||||
resIndex[jss::tx_json][jss::TransactionType]
|
||||
.asString());
|
||||
Json::Value expected;
|
||||
Json::Reader().parse(expected_json, expected);
|
||||
if (RPC::contains_error(expected))
|
||||
Throw<std::runtime_error>(
|
||||
"Internal JSONRPC_test error. Bad test JSON.");
|
||||
|
||||
for (auto memberIt = expected.begin();
|
||||
memberIt != expected.end();
|
||||
memberIt++)
|
||||
{
|
||||
auto const name = memberIt.memberName();
|
||||
if (BEAST_EXPECT(resIndex[jss::tx_json].isMember(name)))
|
||||
{
|
||||
auto const received = resIndex[jss::tx_json][name];
|
||||
BEAST_EXPECTS(
|
||||
received == *memberIt,
|
||||
txhash + " contains \n\"" + name + "\": " //
|
||||
+ to_string(received) //
|
||||
+ " but expected " //
|
||||
+ to_string(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// second request using ledger_hash to lookup and verify
|
||||
@@ -216,8 +235,30 @@ class TransactionEntry_test : public beast::unit_test::suite
|
||||
|
||||
// these are actually AccountSet txs because fund does two txs and
|
||||
// env.tx only reports the last one
|
||||
check_tx(env.closed()->seq(), fund_1_tx);
|
||||
check_tx(env.closed()->seq(), fund_2_tx);
|
||||
check_tx(env.closed()->seq(), fund_1_tx, R"(
|
||||
{
|
||||
"Account" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
|
||||
"Fee" : "10",
|
||||
"Sequence" : 3,
|
||||
"SetFlag" : 8,
|
||||
"SigningPubKey" : "0324CAAFA2212D2AEAB9D42D481535614AED486293E1FB1380FF070C3DD7FB4264",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "3044022007B35E3B99460534FF6BC3A66FBBA03591C355CC38E38588968E87CCD01BE229022071A443026DE45041B55ABB1CC76812A87EA701E475BBB7E165513B4B242D3474",
|
||||
"hash" : "F4E9DF90D829A9E8B423FF68C34413E240D8D8BB0EFD080DF08114ED398E2506"
|
||||
}
|
||||
)");
|
||||
check_tx(env.closed()->seq(), fund_2_tx, R"(
|
||||
{
|
||||
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
|
||||
"Fee" : "10",
|
||||
"Sequence" : 3,
|
||||
"SetFlag" : 8,
|
||||
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
|
||||
"TransactionType" : "AccountSet",
|
||||
"TxnSignature" : "3045022100C8857FC0759A2AC0D2F320684691A66EAD252EAED9EF88C79791BC58BFCC9D860220421722286487DD0ED6BBA626CE6FCBDD14289F7F4726870C3465A4054C2702D7",
|
||||
"hash" : "6853CD8226A05068C951CB1F54889FF4E40C5B440DC1C5BA38F114C4E0B1E705"
|
||||
}
|
||||
)");
|
||||
|
||||
env.trust(A2["USD"](1000), A1);
|
||||
// the trust tx is actually a payment since the trust method
|
||||
@@ -231,16 +272,70 @@ class TransactionEntry_test : public beast::unit_test::suite
|
||||
boost::lexical_cast<std::string>(env.tx()->getTransactionID());
|
||||
env.close();
|
||||
|
||||
check_tx(env.closed()->seq(), trust_tx);
|
||||
check_tx(env.closed()->seq(), pay_tx, jss::Payment.c_str());
|
||||
check_tx(env.closed()->seq(), trust_tx, R"(
|
||||
{
|
||||
"Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"DeliverMax" : "10",
|
||||
"Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 3,
|
||||
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TransactionType" : "Payment",
|
||||
"TxnSignature" : "3044022033D9EBF7F02950AF2F6B13C07AEE641C8FEBDD540A338FCB9027A965A4AED35B02206E4E227DCC226A3456C0FEF953449D21645A24EB63CA0BB7C5B62470147FD1D1",
|
||||
"hash" : "C992D97D88FF444A1AB0C06B27557EC54B7F7DA28254778E60238BEA88E0C101"
|
||||
}
|
||||
)");
|
||||
|
||||
check_tx(
|
||||
env.closed()->seq(),
|
||||
pay_tx,
|
||||
R"(
|
||||
{
|
||||
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
|
||||
"DeliverMax" :
|
||||
{
|
||||
"currency" : "USD",
|
||||
"issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
|
||||
"value" : "5"
|
||||
},
|
||||
"Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
|
||||
"Fee" : "10",
|
||||
"Flags" : 2147483648,
|
||||
"Sequence" : 4,
|
||||
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
|
||||
"TransactionType" : "Payment",
|
||||
"TxnSignature" : "30450221008A722B7F16EDB2348886E88ED4EC682AE9973CC1EE0FF37C93BB2CEC821D3EDF022059E464472031BA5E0D88A93E944B6A8B8DB3E1D5E5D1399A805F615789DB0BED",
|
||||
"hash" : "988046D484ACE9F5F6A8C792D89C6EA2DB307B5DDA9864AEBA88E6782ABD0865"
|
||||
}
|
||||
)");
|
||||
|
||||
env(offer(A2, XRP(100), A2["USD"](1)));
|
||||
auto offer_tx =
|
||||
boost::lexical_cast<std::string>(env.tx()->getTransactionID());
|
||||
|
||||
env.close();
|
||||
|
||||
check_tx(env.closed()->seq(), offer_tx, jss::OfferCreate.c_str());
|
||||
check_tx(
|
||||
env.closed()->seq(),
|
||||
offer_tx,
|
||||
R"(
|
||||
{
|
||||
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
|
||||
"Fee" : "10",
|
||||
"Sequence" : 5,
|
||||
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
|
||||
"TakerGets" :
|
||||
{
|
||||
"currency" : "USD",
|
||||
"issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
|
||||
"value" : "1"
|
||||
},
|
||||
"TakerPays" : "100000000",
|
||||
"TransactionType" : "OfferCreate",
|
||||
"TxnSignature" : "304502210093FC93ACB77B4E3DE3315441BD010096734859080C1797AB735EB47EBD541BD102205020BB1A7C3B4141279EE4C287C13671E2450EA78914EFD0C6DB2A18344CD4F2",
|
||||
"hash" : "5FCC1A27A7664F82A0CC4BE5766FBBB7C560D52B93AA7B550CD33B27AEC7EFFB"
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <ripple/app/rdb/backend/SQLiteDatabase.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/STBase.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/rpc/CTID.h>
|
||||
#include <optional>
|
||||
@@ -719,6 +720,60 @@ class Transaction_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRequest(FeatureBitset features)
|
||||
{
|
||||
testcase("Test Request");
|
||||
|
||||
using namespace test::jtx;
|
||||
using std::to_string;
|
||||
|
||||
const char* COMMAND = jss::tx.c_str();
|
||||
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
Account const alie{"alie"};
|
||||
Account const gw{"gw"};
|
||||
auto const USD{gw["USD"]};
|
||||
|
||||
env.fund(XRP(1000000), alice, gw);
|
||||
env.close();
|
||||
|
||||
// AccountSet
|
||||
env(noop(alice));
|
||||
|
||||
// Payment
|
||||
env(pay(alice, gw, XRP(100)));
|
||||
|
||||
std::shared_ptr<STTx const> txn = env.tx();
|
||||
env.close();
|
||||
std::shared_ptr<STObject const> meta =
|
||||
env.closed()->txRead(env.tx()->getTransactionID()).second;
|
||||
|
||||
Json::Value expected = txn->getJson(JsonOptions::none);
|
||||
expected[jss::DeliverMax] = expected[jss::Amount];
|
||||
|
||||
auto const result =
|
||||
env.rpc(COMMAND, to_string(txn->getTransactionID()));
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
|
||||
|
||||
for (auto memberIt = expected.begin(); memberIt != expected.end();
|
||||
memberIt++)
|
||||
{
|
||||
std::string const name = memberIt.memberName();
|
||||
if (BEAST_EXPECT(result[jss::result].isMember(name)))
|
||||
{
|
||||
auto const received = result[jss::result][name];
|
||||
BEAST_EXPECTS(
|
||||
received == *memberIt,
|
||||
"Transaction contains \n\"" + name + "\": " //
|
||||
+ to_string(received) //
|
||||
+ " but expected " //
|
||||
+ to_string(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -735,6 +790,7 @@ public:
|
||||
testRangeCTIDRequest(features);
|
||||
testCTIDValidation(features);
|
||||
testCTIDRPC(features);
|
||||
testRequest(features);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user