mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-29 06:55:49 +00:00
TxQ full queue RPC info (RIPD-1404):
* RPC `ledger` command returns all queue entries in "queue_data" when requesting open ledger, and including boolean "queue: true". * Includes queue state. e.g.: fee_level, retries, last_result, tx. * Respects "expand" and "binary" parameters for the txs. * Remove some unused code.
This commit is contained in:
committed by
Scott Schurr
parent
846723d771
commit
7265729446
@@ -21,6 +21,7 @@
|
|||||||
#define RIPPLE_APP_LEDGER_LEDGERTOJSON_H_INCLUDED
|
#define RIPPLE_APP_LEDGER_LEDGERTOJSON_H_INCLUDED
|
||||||
|
|
||||||
#include <ripple/app/ledger/Ledger.h>
|
#include <ripple/app/ledger/Ledger.h>
|
||||||
|
#include <ripple/app/misc/TxQ.h>
|
||||||
#include <ripple/basics/StringUtilities.h>
|
#include <ripple/basics/StringUtilities.h>
|
||||||
#include <ripple/protocol/JsonFields.h>
|
#include <ripple/protocol/JsonFields.h>
|
||||||
#include <ripple/protocol/STTx.h>
|
#include <ripple/protocol/STTx.h>
|
||||||
@@ -36,12 +37,27 @@ struct LedgerFill
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LedgerFill(ReadView const& l, int o,
|
||||||
|
std::vector<TxQ::TxDetails> q)
|
||||||
|
: ledger(l)
|
||||||
|
, options(o)
|
||||||
|
, txQueue(q)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
enum Options {
|
enum Options {
|
||||||
dumpTxrp = 1, dumpState = 2, expand = 4, full = 8, binary = 16,
|
dumpTxrp = 1,
|
||||||
ownerFunds = 32};
|
dumpState = 2,
|
||||||
|
expand = 4,
|
||||||
|
full = 8,
|
||||||
|
binary = 16,
|
||||||
|
ownerFunds = 32,
|
||||||
|
dumpQueue = 64
|
||||||
|
};
|
||||||
|
|
||||||
ReadView const& ledger;
|
ReadView const& ledger;
|
||||||
int options;
|
int options;
|
||||||
|
std::vector<TxQ::TxDetails> txQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Given a Ledger and options, fill a Json::Object or Json::Value with a
|
/** Given a Ledger and options, fill a Json::Object or Json::Value with a
|
||||||
@@ -49,7 +65,6 @@ struct LedgerFill
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void addJson(Json::Value&, LedgerFill const&);
|
void addJson(Json::Value&, LedgerFill const&);
|
||||||
void addJson(Json::Object&, LedgerFill const&);
|
|
||||||
|
|
||||||
/** Return a new Json::Value representing the ledger with given options.*/
|
/** Return a new Json::Value representing the ledger with given options.*/
|
||||||
Json::Value getJson (LedgerFill const&);
|
Json::Value getJson (LedgerFill const&);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <ripple/app/ledger/LedgerToJson.h>
|
#include <ripple/app/ledger/LedgerToJson.h>
|
||||||
|
#include <ripple/app/misc/TxQ.h>
|
||||||
#include <ripple/basics/base_uint.h>
|
#include <ripple/basics/base_uint.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
@@ -95,24 +96,18 @@ void fillJsonBinary(Object& json, bool closed, LedgerInfo const& info)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Object>
|
Json::Value fillJsonTx (LedgerFill const& fill,
|
||||||
void fillJsonTx (Object& json, LedgerFill const& fill)
|
bool bBinary, bool bExpanded,
|
||||||
{
|
std::pair<std::shared_ptr<STTx const>,
|
||||||
auto&& txns = setArray (json, jss::transactions);
|
std::shared_ptr<STObject const>> const i)
|
||||||
auto bBinary = isBinary(fill);
|
|
||||||
auto bExpanded = isExpanded(fill);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (auto& i: fill.ledger.txs)
|
|
||||||
{
|
{
|
||||||
if (! bExpanded)
|
if (! bExpanded)
|
||||||
{
|
{
|
||||||
txns.append(to_string(i.first->getTransactionID()));
|
return to_string(i.first->getTransactionID());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto&& txJson = appendObject(txns);
|
Json::Value txJson{ Json::objectValue };
|
||||||
if (bBinary)
|
if (bBinary)
|
||||||
{
|
{
|
||||||
txJson[jss::tx_blob] = serializeHex(*i.first);
|
txJson[jss::tx_blob] = serializeHex(*i.first);
|
||||||
@@ -141,8 +136,23 @@ void fillJsonTx (Object& json, LedgerFill const& fill)
|
|||||||
txJson[jss::owner_funds] = ownerFunds.getText ();
|
txJson[jss::owner_funds] = ownerFunds.getText ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return txJson;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class Object>
|
||||||
|
void fillJsonTx (Object& json, LedgerFill const& fill)
|
||||||
|
{
|
||||||
|
auto&& txns = setArray (json, jss::transactions);
|
||||||
|
auto bBinary = isBinary(fill);
|
||||||
|
auto bExpanded = isExpanded(fill);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (auto& i: fill.ledger.txs)
|
||||||
|
{
|
||||||
|
txns.append(fillJsonTx(fill, bBinary, bExpanded, i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception const&)
|
catch (std::exception const&)
|
||||||
{
|
{
|
||||||
@@ -173,6 +183,42 @@ void fillJsonState(Object& json, LedgerFill const& fill)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class Object>
|
||||||
|
void fillJsonQueue(Object& json, LedgerFill const& fill)
|
||||||
|
{
|
||||||
|
auto&& queueData = Json::setArray(json, jss::queue_data);
|
||||||
|
auto bBinary = isBinary(fill);
|
||||||
|
auto bExpanded = isExpanded(fill);
|
||||||
|
|
||||||
|
for (auto const& tx : fill.txQueue)
|
||||||
|
{
|
||||||
|
auto&& txJson = appendObject(queueData);
|
||||||
|
txJson[jss::fee_level] = to_string(tx.feeLevel);
|
||||||
|
if (tx.lastValid)
|
||||||
|
txJson[jss::LastLedgerSequence] = *tx.lastValid;
|
||||||
|
if (tx.consequences)
|
||||||
|
{
|
||||||
|
txJson[jss::fee] = to_string(
|
||||||
|
tx.consequences->fee);
|
||||||
|
auto spend = tx.consequences->potentialSpend +
|
||||||
|
tx.consequences->fee;
|
||||||
|
txJson[jss::max_spend_drops] = to_string(spend);
|
||||||
|
auto authChanged = tx.consequences->category ==
|
||||||
|
TxConsequences::blocker;
|
||||||
|
txJson[jss::auth_change] = authChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
txJson[jss::account] = to_string(tx.account);
|
||||||
|
txJson["retries_remaining"] = tx.retriesRemaining;
|
||||||
|
txJson["preflight_result"] = transToken(tx.preflightResult);
|
||||||
|
if (tx.lastResult)
|
||||||
|
txJson["last_result"] = transToken(*tx.lastResult);
|
||||||
|
|
||||||
|
txJson[jss::tx] = fillJsonTx(fill, bBinary, bExpanded,
|
||||||
|
std::make_pair(tx.txn, nullptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class Object>
|
template <class Object>
|
||||||
void fillJson (Object& json, LedgerFill const& fill)
|
void fillJson (Object& json, LedgerFill const& fill)
|
||||||
{
|
{
|
||||||
@@ -193,17 +239,13 @@ void fillJson (Object& json, LedgerFill const& fill)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void addJson (Json::Object& json, LedgerFill const& fill)
|
|
||||||
{
|
|
||||||
auto&& object = Json::addObject (json, jss::ledger);
|
|
||||||
fillJson (object, fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addJson (Json::Value& json, LedgerFill const& fill)
|
void addJson (Json::Value& json, LedgerFill const& fill)
|
||||||
{
|
{
|
||||||
auto&& object = Json::addObject (json, jss::ledger);
|
auto&& object = Json::addObject (json, jss::ledger);
|
||||||
fillJson (object, fill);
|
fillJson (object, fill);
|
||||||
|
|
||||||
|
if ((fill.options & LedgerFill::dumpQueue) && !fill.txQueue.empty())
|
||||||
|
fillJsonQueue(json, fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value getJson (LedgerFill const& fill)
|
Json::Value getJson (LedgerFill const& fill)
|
||||||
|
|||||||
@@ -96,6 +96,15 @@ public:
|
|||||||
boost::optional<TxConsequences const> consequences;
|
boost::optional<TxConsequences const> consequences;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TxDetails : AccountTxDetails
|
||||||
|
{
|
||||||
|
AccountID account;
|
||||||
|
std::shared_ptr<STTx const> txn;
|
||||||
|
int retriesRemaining;
|
||||||
|
TER preflightResult;
|
||||||
|
boost::optional<TER> lastResult;
|
||||||
|
};
|
||||||
|
|
||||||
TxQ(Setup const& setup,
|
TxQ(Setup const& setup,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
@@ -149,9 +158,19 @@ public:
|
|||||||
amendment is not enabled, OR if the account has no transactions
|
amendment is not enabled, OR if the account has no transactions
|
||||||
in the queue.
|
in the queue.
|
||||||
*/
|
*/
|
||||||
boost::optional<std::map<TxSeq, AccountTxDetails>>
|
std::map<TxSeq, AccountTxDetails const>
|
||||||
getAccountTxs(AccountID const& account, ReadView const& view) const;
|
getAccountTxs(AccountID const& account, ReadView const& view) const;
|
||||||
|
|
||||||
|
/** Returns information about all transactions currently
|
||||||
|
in the queue.
|
||||||
|
|
||||||
|
@returns Uninitialized @ref optional if the FeeEscalation
|
||||||
|
amendment is not enabled, OR if there are no transactions
|
||||||
|
in the queue.
|
||||||
|
*/
|
||||||
|
std::vector<TxDetails>
|
||||||
|
getTxs(ReadView const& view) const;
|
||||||
|
|
||||||
/** Packages up fee metrics for the `fee` RPC command.
|
/** Packages up fee metrics for the `fee` RPC command.
|
||||||
*/
|
*/
|
||||||
Json::Value
|
Json::Value
|
||||||
@@ -267,6 +286,7 @@ private:
|
|||||||
int retriesRemaining;
|
int retriesRemaining;
|
||||||
TxSeq const sequence;
|
TxSeq const sequence;
|
||||||
ApplyFlags const flags;
|
ApplyFlags const flags;
|
||||||
|
boost::optional<TER> lastResult;
|
||||||
// Invariant: pfresult is never allowed to be empty. The
|
// Invariant: pfresult is never allowed to be empty. The
|
||||||
// boost::optional is leveraged to allow `emplace`d
|
// boost::optional is leveraged to allow `emplace`d
|
||||||
// construction and replacement without a copy
|
// construction and replacement without a copy
|
||||||
|
|||||||
@@ -492,6 +492,7 @@ TxQ::tryClearAccountQueue(Application& app, OpenView& view,
|
|||||||
// succeeds, the MaybeTx will be destructed, so it'll be
|
// succeeds, the MaybeTx will be destructed, so it'll be
|
||||||
// moot.
|
// moot.
|
||||||
--it->second.retriesRemaining;
|
--it->second.retriesRemaining;
|
||||||
|
it->second.lastResult = txResult.first;
|
||||||
if (!txResult.second)
|
if (!txResult.second)
|
||||||
{
|
{
|
||||||
// Transaction failed to apply. Fall back to the normal process.
|
// Transaction failed to apply. Fall back to the normal process.
|
||||||
@@ -1268,6 +1269,7 @@ TxQ::accept(Application& app,
|
|||||||
candidateIter->retriesRemaining = 1;
|
candidateIter->retriesRemaining = 1;
|
||||||
else
|
else
|
||||||
--candidateIter->retriesRemaining;
|
--candidateIter->retriesRemaining;
|
||||||
|
candidateIter->lastResult = txnResult;
|
||||||
if (account.dropPenalty &&
|
if (account.dropPenalty &&
|
||||||
account.transactions.size() > 1 && isFull<95>())
|
account.transactions.size() > 1 && isFull<95>())
|
||||||
{
|
{
|
||||||
@@ -1331,34 +1333,79 @@ TxQ::getMetrics(OpenView const& view, std::uint32_t txCountPadding) const
|
|||||||
|
|
||||||
auto
|
auto
|
||||||
TxQ::getAccountTxs(AccountID const& account, ReadView const& view) const
|
TxQ::getAccountTxs(AccountID const& account, ReadView const& view) const
|
||||||
-> boost::optional<std::map<TxSeq, AccountTxDetails>>
|
-> std::map<TxSeq, AccountTxDetails const>
|
||||||
{
|
{
|
||||||
auto const allowEscalation =
|
auto const allowEscalation =
|
||||||
(view.rules().enabled(featureFeeEscalation));
|
(view.rules().enabled(featureFeeEscalation));
|
||||||
if (!allowEscalation)
|
if (!allowEscalation)
|
||||||
return boost::none;
|
return {};
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
auto accountIter = byAccount_.find(account);
|
auto accountIter = byAccount_.find(account);
|
||||||
if (accountIter == byAccount_.end() ||
|
if (accountIter == byAccount_.end() ||
|
||||||
accountIter->second.transactions.empty())
|
accountIter->second.transactions.empty())
|
||||||
return boost::none;
|
return {};
|
||||||
|
|
||||||
std::map<TxSeq, AccountTxDetails> result;
|
std::map<TxSeq, AccountTxDetails const> result;
|
||||||
|
|
||||||
for (auto const& tx : accountIter->second.transactions)
|
for (auto const& tx : accountIter->second.transactions)
|
||||||
{
|
{
|
||||||
auto& resultTx = result[tx.first];
|
result.emplace(tx.first, [&]
|
||||||
|
{
|
||||||
|
AccountTxDetails resultTx;
|
||||||
resultTx.feeLevel = tx.second.feeLevel;
|
resultTx.feeLevel = tx.second.feeLevel;
|
||||||
if (tx.second.lastValid)
|
if (tx.second.lastValid)
|
||||||
resultTx.lastValid.emplace(*tx.second.lastValid);
|
resultTx.lastValid.emplace(*tx.second.lastValid);
|
||||||
if (tx.second.consequences)
|
if (tx.second.consequences)
|
||||||
resultTx.consequences.emplace(*tx.second.consequences);
|
resultTx.consequences.emplace(*tx.second.consequences);
|
||||||
|
return resultTx;
|
||||||
|
}());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
TxQ::getTxs(ReadView const& view) const
|
||||||
|
-> std::vector<TxDetails>
|
||||||
|
{
|
||||||
|
auto const allowEscalation =
|
||||||
|
(view.rules().enabled(featureFeeEscalation));
|
||||||
|
if (!allowEscalation)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (byFee_.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<TxDetails> result;
|
||||||
|
result.reserve(byFee_.size());
|
||||||
|
|
||||||
|
for (auto const& tx : byFee_)
|
||||||
|
{
|
||||||
|
result.emplace_back([&]
|
||||||
|
{
|
||||||
|
TxDetails resultTx;
|
||||||
|
resultTx.feeLevel = tx.feeLevel;
|
||||||
|
if (tx.lastValid)
|
||||||
|
resultTx.lastValid.emplace(*tx.lastValid);
|
||||||
|
if (tx.consequences)
|
||||||
|
resultTx.consequences.emplace(*tx.consequences);
|
||||||
|
resultTx.account = tx.account;
|
||||||
|
resultTx.txn = tx.txn;
|
||||||
|
resultTx.retriesRemaining = tx.retriesRemaining;
|
||||||
|
BOOST_ASSERT(tx.pfresult);
|
||||||
|
resultTx.preflightResult = tx.pfresult->ter;
|
||||||
|
if (tx.lastResult)
|
||||||
|
resultTx.lastResult.emplace(*tx.lastResult);
|
||||||
|
return resultTx;
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Json::Value
|
Json::Value
|
||||||
TxQ::doRPC(Application& app) const
|
TxQ::doRPC(Application& app) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ struct Context;
|
|||||||
/** Execute an RPC command and store the results in a Json::Value. */
|
/** Execute an RPC command and store the results in a Json::Value. */
|
||||||
Status doCommand (RPC::Context&, Json::Value&);
|
Status doCommand (RPC::Context&, Json::Value&);
|
||||||
|
|
||||||
/** Execute an RPC command and store the results in an std::string. */
|
|
||||||
void executeRPC (RPC::Context&, std::string&);
|
|
||||||
|
|
||||||
Role roleRequired (std::string const& method );
|
Role roleRequired (std::string const& method );
|
||||||
|
|
||||||
} // RPC
|
} // RPC
|
||||||
|
|||||||
@@ -117,11 +117,11 @@ Json::Value doAccountInfo (RPC::Context& context)
|
|||||||
|
|
||||||
auto const txs = context.app.getTxQ().getAccountTxs(
|
auto const txs = context.app.getTxQ().getAccountTxs(
|
||||||
accountID, *ledger);
|
accountID, *ledger);
|
||||||
if (txs && !txs->empty())
|
if (!txs.empty())
|
||||||
{
|
{
|
||||||
jvQueueData[jss::txn_count] = static_cast<Json::UInt>(txs->size());
|
jvQueueData[jss::txn_count] = static_cast<Json::UInt>(txs.size());
|
||||||
jvQueueData[jss::lowest_sequence] = txs->begin()->first;
|
jvQueueData[jss::lowest_sequence] = txs.begin()->first;
|
||||||
jvQueueData[jss::highest_sequence] = txs->rbegin()->first;
|
jvQueueData[jss::highest_sequence] = txs.rbegin()->first;
|
||||||
|
|
||||||
auto& jvQueueTx = jvQueueData[jss::transactions];
|
auto& jvQueueTx = jvQueueData[jss::transactions];
|
||||||
jvQueueTx = Json::arrayValue;
|
jvQueueTx = Json::arrayValue;
|
||||||
@@ -129,7 +129,7 @@ Json::Value doAccountInfo (RPC::Context& context)
|
|||||||
boost::optional<bool> anyAuthChanged(false);
|
boost::optional<bool> anyAuthChanged(false);
|
||||||
boost::optional<XRPAmount> totalSpend(0);
|
boost::optional<XRPAmount> totalSpend(0);
|
||||||
|
|
||||||
for (auto const& tx : *txs)
|
for (auto const& tx : txs)
|
||||||
{
|
{
|
||||||
Json::Value jvTx = Json::objectValue;
|
Json::Value jvTx = Json::objectValue;
|
||||||
|
|
||||||
|
|||||||
@@ -48,21 +48,23 @@ Status LedgerHandler::check()
|
|||||||
if (auto s = lookupLedger (ledger_, context_, result_))
|
if (auto s = lookupLedger (ledger_, context_, result_))
|
||||||
return s;
|
return s;
|
||||||
|
|
||||||
bool bFull = params[jss::full].asBool();
|
bool const full = params[jss::full].asBool();
|
||||||
bool bTransactions = params[jss::transactions].asBool();
|
bool const transactions = params[jss::transactions].asBool();
|
||||||
bool bAccounts = params[jss::accounts].asBool();
|
bool const accounts = params[jss::accounts].asBool();
|
||||||
bool bExpand = params[jss::expand].asBool();
|
bool const expand = params[jss::expand].asBool();
|
||||||
bool bBinary = params[jss::binary].asBool();
|
bool const binary = params[jss::binary].asBool();
|
||||||
bool const owner_funds = params[jss::owner_funds].asBool();
|
bool const owner_funds = params[jss::owner_funds].asBool();
|
||||||
|
bool const queue = params[jss::queue].asBool();
|
||||||
|
|
||||||
options_ = (bFull ? LedgerFill::full : 0)
|
options_ = (full ? LedgerFill::full : 0)
|
||||||
| (bExpand ? LedgerFill::expand : 0)
|
| (expand ? LedgerFill::expand : 0)
|
||||||
| (bTransactions ? LedgerFill::dumpTxrp : 0)
|
| (transactions ? LedgerFill::dumpTxrp : 0)
|
||||||
| (bAccounts ? LedgerFill::dumpState : 0)
|
| (accounts ? LedgerFill::dumpState : 0)
|
||||||
| (bBinary ? LedgerFill::binary : 0)
|
| (binary ? LedgerFill::binary : 0)
|
||||||
| (owner_funds ? LedgerFill::ownerFunds : 0);
|
| (owner_funds ? LedgerFill::ownerFunds : 0)
|
||||||
|
| (queue ? LedgerFill::dumpQueue : 0);
|
||||||
|
|
||||||
if (bFull || bAccounts)
|
if (full || accounts)
|
||||||
{
|
{
|
||||||
// Until some sane way to get full ledgers has been implemented,
|
// Until some sane way to get full ledgers has been implemented,
|
||||||
// disallow retrieving all state nodes.
|
// disallow retrieving all state nodes.
|
||||||
@@ -74,9 +76,20 @@ Status LedgerHandler::check()
|
|||||||
{
|
{
|
||||||
return rpcTOO_BUSY;
|
return rpcTOO_BUSY;
|
||||||
}
|
}
|
||||||
context_.loadType = bBinary ? Resource::feeMediumBurdenRPC :
|
context_.loadType = binary ? Resource::feeMediumBurdenRPC :
|
||||||
Resource::feeHighBurdenRPC;
|
Resource::feeHighBurdenRPC;
|
||||||
}
|
}
|
||||||
|
if (queue)
|
||||||
|
{
|
||||||
|
if (! ledger_ || ! ledger_->open())
|
||||||
|
{
|
||||||
|
// It doesn't make sense to request the queue
|
||||||
|
// with a non-existant or closed/validated ledger.
|
||||||
|
return rpcINVALID_PARAMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueTxs_ = context_.app.getTxQ().getTxs(*ledger_);
|
||||||
|
}
|
||||||
|
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
Context& context_;
|
Context& context_;
|
||||||
std::shared_ptr<ReadView const> ledger_;
|
std::shared_ptr<ReadView const> ledger_;
|
||||||
|
std::vector<TxQ::TxDetails> queueTxs_;
|
||||||
Json::Value result_;
|
Json::Value result_;
|
||||||
int options_ = 0;
|
int options_ = 0;
|
||||||
};
|
};
|
||||||
@@ -88,7 +89,7 @@ void LedgerHandler::writeResult (Object& value)
|
|||||||
if (ledger_)
|
if (ledger_)
|
||||||
{
|
{
|
||||||
Json::copyFrom (value, result_);
|
Json::copyFrom (value, result_);
|
||||||
addJson (value, {*ledger_, options_});
|
addJson (value, {*ledger_, options_, queueTxs_});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ class HandlerTable {
|
|||||||
h.valueMethod_ = &handle<Json::Value, HandlerImpl>;
|
h.valueMethod_ = &handle<Json::Value, HandlerImpl>;
|
||||||
h.role_ = HandlerImpl::role();
|
h.role_ = HandlerImpl::role();
|
||||||
h.condition_ = HandlerImpl::condition();
|
h.condition_ = HandlerImpl::condition();
|
||||||
h.objectMethod_ = &handle<Json::Object, HandlerImpl>;
|
|
||||||
|
|
||||||
table_[HandlerImpl::name()] = h;
|
table_[HandlerImpl::name()] = h;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ struct Handler
|
|||||||
Method<Json::Value> valueMethod_;
|
Method<Json::Value> valueMethod_;
|
||||||
Role role_;
|
Role role_;
|
||||||
RPC::Condition condition_;
|
RPC::Condition condition_;
|
||||||
Method<Json::Object> objectMethod_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Handler* getHandler (std::string const&);
|
const Handler* getHandler (std::string const&);
|
||||||
|
|||||||
@@ -265,36 +265,6 @@ Status doCommand (
|
|||||||
return rpcUNKNOWN_COMMAND;
|
return rpcUNKNOWN_COMMAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Execute an RPC command and store the results in a string. */
|
|
||||||
void executeRPC (
|
|
||||||
RPC::Context& context, std::string& output)
|
|
||||||
{
|
|
||||||
boost::optional <Handler const&> handler;
|
|
||||||
if (auto error = fillHandler (context, handler))
|
|
||||||
{
|
|
||||||
auto wo = Json::stringWriterObject (output);
|
|
||||||
auto&& sub = Json::addObject (*wo, jss::result);
|
|
||||||
inject_error (error, sub);
|
|
||||||
}
|
|
||||||
else if (auto method = handler->objectMethod_)
|
|
||||||
{
|
|
||||||
auto wo = Json::stringWriterObject (output);
|
|
||||||
getResult (context, method, *wo, handler->name_);
|
|
||||||
}
|
|
||||||
else if (auto method = handler->valueMethod_)
|
|
||||||
{
|
|
||||||
auto object = Json::Value (Json::objectValue);
|
|
||||||
getResult (context, method, object, handler->name_);
|
|
||||||
output = to_string (object);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Can't ever get here.
|
|
||||||
assert (false);
|
|
||||||
Throw<std::logic_error> ("RPC handler with no method");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Role roleRequired (std::string const& method)
|
Role roleRequired (std::string const& method)
|
||||||
{
|
{
|
||||||
auto handler = RPC::getHandler(method);
|
auto handler = RPC::getHandler(method);
|
||||||
|
|||||||
@@ -434,8 +434,7 @@ transactionPreProcessImpl (
|
|||||||
*ledger);
|
*ledger);
|
||||||
// If the account has any txs in the TxQ, skip those sequence
|
// If the account has any txs in the TxQ, skip those sequence
|
||||||
// numbers (accounting for possible gaps).
|
// numbers (accounting for possible gaps).
|
||||||
if(queued)
|
for(auto const& tx : queued)
|
||||||
for(auto const& tx : *queued)
|
|
||||||
{
|
{
|
||||||
if (tx.first == seq)
|
if (tx.first == seq)
|
||||||
++seq;
|
++seq;
|
||||||
|
|||||||
@@ -447,27 +447,21 @@ public:
|
|||||||
{
|
{
|
||||||
auto& txQ = env.app().getTxQ();
|
auto& txQ = env.app().getTxQ();
|
||||||
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
||||||
if (BEAST_EXPECT(aliceStat))
|
BEAST_EXPECT(aliceStat.size() == 1);
|
||||||
{
|
BEAST_EXPECT(aliceStat.begin()->second.feeLevel == 256);
|
||||||
BEAST_EXPECT(aliceStat->size() == 1);
|
BEAST_EXPECT(aliceStat.begin()->second.lastValid &&
|
||||||
BEAST_EXPECT(aliceStat->begin()->second.feeLevel == 256);
|
*aliceStat.begin()->second.lastValid == 8);
|
||||||
BEAST_EXPECT(aliceStat->begin()->second.lastValid &&
|
BEAST_EXPECT(!aliceStat.begin()->second.consequences);
|
||||||
*aliceStat->begin()->second.lastValid == 8);
|
|
||||||
BEAST_EXPECT(!aliceStat->begin()->second.consequences);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto bobStat = txQ.getAccountTxs(bob.id(), *env.current());
|
auto bobStat = txQ.getAccountTxs(bob.id(), *env.current());
|
||||||
if (BEAST_EXPECT(bobStat))
|
BEAST_EXPECT(bobStat.size() == 1);
|
||||||
{
|
BEAST_EXPECT(bobStat.begin()->second.feeLevel == 7000 * 256 / 10);
|
||||||
BEAST_EXPECT(bobStat->size() == 1);
|
BEAST_EXPECT(!bobStat.begin()->second.lastValid);
|
||||||
BEAST_EXPECT(bobStat->begin()->second.feeLevel = 7000 * 256 / 10);
|
BEAST_EXPECT(!bobStat.begin()->second.consequences);
|
||||||
BEAST_EXPECT(!bobStat->begin()->second.lastValid);
|
|
||||||
BEAST_EXPECT(!bobStat->begin()->second.consequences);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto noStat = txQ.getAccountTxs(Account::master.id(),
|
auto noStat = txQ.getAccountTxs(Account::master.id(),
|
||||||
*env.current());
|
*env.current());
|
||||||
BEAST_EXPECT(!noStat);
|
BEAST_EXPECT(noStat.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
env.close();
|
env.close();
|
||||||
@@ -792,10 +786,8 @@ public:
|
|||||||
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
||||||
std::int64_t fee = 20;
|
std::int64_t fee = 20;
|
||||||
auto seq = env.seq(alice);
|
auto seq = env.seq(alice);
|
||||||
if (BEAST_EXPECT(aliceStat))
|
BEAST_EXPECT(aliceStat.size() == 7);
|
||||||
{
|
for (auto const& tx : aliceStat)
|
||||||
BEAST_EXPECT(aliceStat->size() == 7);
|
|
||||||
for (auto const& tx : *aliceStat)
|
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(tx.first == seq);
|
BEAST_EXPECT(tx.first == seq);
|
||||||
BEAST_EXPECT(tx.second.feeLevel == mulDiv(fee, 256, 10).second);
|
BEAST_EXPECT(tx.second.feeLevel == mulDiv(fee, 256, 10).second);
|
||||||
@@ -808,7 +800,6 @@ public:
|
|||||||
++seq;
|
++seq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Alice attempts to add another item to the queue,
|
// Alice attempts to add another item to the queue,
|
||||||
// but you can't force your own earlier txn off the
|
// but you can't force your own earlier txn off the
|
||||||
@@ -1823,11 +1814,9 @@ public:
|
|||||||
checkMetrics(env, 5, boost::none, 7, 6, 256);
|
checkMetrics(env, 5, boost::none, 7, 6, 256);
|
||||||
{
|
{
|
||||||
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
||||||
if (BEAST_EXPECT(aliceStat))
|
|
||||||
{
|
|
||||||
auto seq = aliceSeq;
|
auto seq = aliceSeq;
|
||||||
BEAST_EXPECT(aliceStat->size() == 5);
|
BEAST_EXPECT(aliceStat.size() == 5);
|
||||||
for (auto const& tx : *aliceStat)
|
for (auto const& tx : aliceStat)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(tx.first == seq);
|
BEAST_EXPECT(tx.first == seq);
|
||||||
BEAST_EXPECT(tx.second.feeLevel == 25600);
|
BEAST_EXPECT(tx.second.feeLevel == 25600);
|
||||||
@@ -1843,7 +1832,6 @@ public:
|
|||||||
++seq;
|
++seq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Put some txs in the queue for bob.
|
// Put some txs in the queue for bob.
|
||||||
// Give them a higher fee so they'll beat alice's.
|
// Give them a higher fee so they'll beat alice's.
|
||||||
for (int i = 0; i < 8; ++i)
|
for (int i = 0; i < 8; ++i)
|
||||||
@@ -1870,17 +1858,15 @@ public:
|
|||||||
{
|
{
|
||||||
// Bob has nothing left in the queue.
|
// Bob has nothing left in the queue.
|
||||||
auto bobStat = txQ.getAccountTxs(bob.id(), *env.current());
|
auto bobStat = txQ.getAccountTxs(bob.id(), *env.current());
|
||||||
BEAST_EXPECT(!bobStat);
|
BEAST_EXPECT(bobStat.empty());
|
||||||
}
|
}
|
||||||
// Verify alice's tx got dropped as we BEAST_EXPECT, and that there's
|
// Verify alice's tx got dropped as we BEAST_EXPECT, and that there's
|
||||||
// a gap in her queued txs.
|
// a gap in her queued txs.
|
||||||
{
|
{
|
||||||
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
||||||
if (BEAST_EXPECT(aliceStat))
|
|
||||||
{
|
|
||||||
auto seq = aliceSeq;
|
auto seq = aliceSeq;
|
||||||
BEAST_EXPECT(aliceStat->size() == 4);
|
BEAST_EXPECT(aliceStat.size() == 4);
|
||||||
for (auto const& tx : *aliceStat)
|
for (auto const& tx : aliceStat)
|
||||||
{
|
{
|
||||||
// Skip over the missing one.
|
// Skip over the missing one.
|
||||||
if (seq == aliceSeq + 2)
|
if (seq == aliceSeq + 2)
|
||||||
@@ -1892,17 +1878,14 @@ public:
|
|||||||
++seq;
|
++seq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Now, fill the gap.
|
// Now, fill the gap.
|
||||||
envs(noop(alice), fee(none), seq(none), ter(terQUEUED))(params);
|
envs(noop(alice), fee(none), seq(none), ter(terQUEUED))(params);
|
||||||
checkMetrics(env, 5, 18, 10, 9, 256);
|
checkMetrics(env, 5, 18, 10, 9, 256);
|
||||||
{
|
{
|
||||||
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
||||||
if (BEAST_EXPECT(aliceStat))
|
|
||||||
{
|
|
||||||
auto seq = aliceSeq;
|
auto seq = aliceSeq;
|
||||||
BEAST_EXPECT(aliceStat->size() == 5);
|
BEAST_EXPECT(aliceStat.size() == 5);
|
||||||
for (auto const& tx : *aliceStat)
|
for (auto const& tx : aliceStat)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(tx.first == seq);
|
BEAST_EXPECT(tx.first == seq);
|
||||||
BEAST_EXPECT(tx.second.feeLevel == 25600);
|
BEAST_EXPECT(tx.second.feeLevel == 25600);
|
||||||
@@ -1910,18 +1893,17 @@ public:
|
|||||||
++seq;
|
++seq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
env.close();
|
env.close();
|
||||||
checkMetrics(env, 0, 20, 5, 10, 256);
|
checkMetrics(env, 0, 20, 5, 10, 256);
|
||||||
{
|
{
|
||||||
// Bob's data has been cleaned up.
|
// Bob's data has been cleaned up.
|
||||||
auto bobStat = txQ.getAccountTxs(bob.id(), *env.current());
|
auto bobStat = txQ.getAccountTxs(bob.id(), *env.current());
|
||||||
BEAST_EXPECT(!bobStat);
|
BEAST_EXPECT(bobStat.empty());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current());
|
||||||
BEAST_EXPECT(!aliceStat);
|
BEAST_EXPECT(aliceStat.empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2723,17 +2705,14 @@ public:
|
|||||||
checkMetrics(env, 2, 24, 16, 12, 256);
|
checkMetrics(env, 2, 24, 16, 12, 256);
|
||||||
auto const aliceQueue = env.app().getTxQ().getAccountTxs(
|
auto const aliceQueue = env.app().getTxQ().getAccountTxs(
|
||||||
alice.id(), *env.current());
|
alice.id(), *env.current());
|
||||||
if (BEAST_EXPECT(aliceQueue))
|
BEAST_EXPECT(aliceQueue.size() == 2);
|
||||||
{
|
|
||||||
BEAST_EXPECT(aliceQueue->size() == 2);
|
|
||||||
auto seq = aliceSeq;
|
auto seq = aliceSeq;
|
||||||
for (auto const& tx : *aliceQueue)
|
for (auto const& tx : aliceQueue)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(tx.first == seq);
|
BEAST_EXPECT(tx.first == seq);
|
||||||
BEAST_EXPECT(tx.second.feeLevel == 2560);
|
BEAST_EXPECT(tx.second.feeLevel == 2560);
|
||||||
++seq;
|
++seq;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Close the ledger to clear the queue
|
// Close the ledger to clear the queue
|
||||||
env.close();
|
env.close();
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
|
#include <ripple/app/misc/TxQ.h>
|
||||||
#include <ripple/protocol/ErrorCodes.h>
|
#include <ripple/protocol/ErrorCodes.h>
|
||||||
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/JsonFields.h>
|
#include <ripple/protocol/JsonFields.h>
|
||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
#include <ripple/beast/unit_test.h>
|
#include <ripple/beast/unit_test.h>
|
||||||
@@ -110,6 +112,15 @@ class LedgerRPC_test : public beast::unit_test::suite
|
|||||||
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
|
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Request queue for closed ledger
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::ledger_index] = "validated";
|
||||||
|
jvParams[jss::queue] = true;
|
||||||
|
auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result];
|
||||||
|
checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void testLedgerCurrent()
|
void testLedgerCurrent()
|
||||||
@@ -440,6 +451,207 @@ class LedgerRPC_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testNoQueue()
|
||||||
|
{
|
||||||
|
testcase("Ledger with queueing disabled");
|
||||||
|
using namespace test::jtx;
|
||||||
|
Env env{ *this };
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::ledger_index] = "current";
|
||||||
|
jv[jss::queue] = true;
|
||||||
|
jv[jss::expand] = true;
|
||||||
|
|
||||||
|
auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
BEAST_EXPECT(! jrr.isMember(jss::queue_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void testQueue()
|
||||||
|
{
|
||||||
|
testcase("Ledger with Queued Transactions");
|
||||||
|
using namespace test::jtx;
|
||||||
|
Env env{ *this, []()
|
||||||
|
{
|
||||||
|
auto p = std::make_unique<Config>();
|
||||||
|
test::setupConfigForUnitTests(*p);
|
||||||
|
auto& section = p->section("transaction_queue");
|
||||||
|
section.set("minimum_txn_in_ledger_standalone", "3");
|
||||||
|
return p;
|
||||||
|
}(),
|
||||||
|
features(featureFeeEscalation) };
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::ledger_index] = "current";
|
||||||
|
jv[jss::queue] = true;
|
||||||
|
jv[jss::expand] = true;
|
||||||
|
|
||||||
|
Account const alice{ "alice" };
|
||||||
|
Account const bob{ "bob" };
|
||||||
|
Account const charlie{ "charlie" };
|
||||||
|
Account const daria{ "daria" };
|
||||||
|
env.fund(XRP(10000), alice);
|
||||||
|
env.fund(XRP(10000), bob);
|
||||||
|
env.close();
|
||||||
|
env.fund(XRP(10000), charlie);
|
||||||
|
env.fund(XRP(10000), daria);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
BEAST_EXPECT(! jrr.isMember(jss::queue_data));
|
||||||
|
|
||||||
|
// Fill the open ledger
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
auto metrics = env.app().getTxQ().getMetrics(*env.current());
|
||||||
|
if (! BEAST_EXPECT(metrics))
|
||||||
|
break;
|
||||||
|
if (metrics->expFeeLevel > metrics->minFeeLevel)
|
||||||
|
break;
|
||||||
|
env(noop(alice));
|
||||||
|
}
|
||||||
|
|
||||||
|
BEAST_EXPECT(env.current()->info().seq == 5);
|
||||||
|
// Put some txs in the queue
|
||||||
|
// Alice
|
||||||
|
auto aliceSeq = env.seq(alice);
|
||||||
|
env(pay(alice, "george", XRP(1000)), json(R"({"LastLedgerSequence":7})"),
|
||||||
|
ter(terQUEUED));
|
||||||
|
env(offer(alice, XRP(50000), alice["USD"](5000)), seq(aliceSeq + 1),
|
||||||
|
ter(terQUEUED));
|
||||||
|
env(noop(alice), seq(aliceSeq + 2), ter(terQUEUED));
|
||||||
|
// Bob
|
||||||
|
auto batch = [&env](Account a)
|
||||||
|
{
|
||||||
|
auto aSeq = env.seq(a);
|
||||||
|
// Enough fee to get in front of alice in the queue
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
{
|
||||||
|
env(noop(a), fee(1000 + i), seq(aSeq + i), ter(terQUEUED));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
batch(bob);
|
||||||
|
// Charlie
|
||||||
|
batch(charlie);
|
||||||
|
// Daria
|
||||||
|
batch(daria);
|
||||||
|
|
||||||
|
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
BEAST_EXPECT(jrr[jss::queue_data].size() == 33);
|
||||||
|
|
||||||
|
// Close enough ledgers so that alice's first tx expires.
|
||||||
|
env.close();
|
||||||
|
env.close();
|
||||||
|
env.close();
|
||||||
|
BEAST_EXPECT(env.current()->info().seq == 8);
|
||||||
|
|
||||||
|
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
BEAST_EXPECT(jrr[jss::queue_data].size() == 11);
|
||||||
|
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
std::string txid1;
|
||||||
|
std::string txid2;
|
||||||
|
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
|
||||||
|
{
|
||||||
|
auto const& txj = jrr[jss::queue_data][0u];
|
||||||
|
BEAST_EXPECT(txj[jss::account] == alice.human());
|
||||||
|
BEAST_EXPECT(txj[jss::fee_level] == "256");
|
||||||
|
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
|
||||||
|
BEAST_EXPECT(txj["retries_remaining"] == 10);
|
||||||
|
BEAST_EXPECT(txj.isMember(jss::tx));
|
||||||
|
auto const& tx = txj[jss::tx];
|
||||||
|
BEAST_EXPECT(tx[jss::Account] == alice.human());
|
||||||
|
BEAST_EXPECT(tx[jss::TransactionType] == "OfferCreate");
|
||||||
|
txid1 = tx[jss::hash].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
jv[jss::expand] = false;
|
||||||
|
|
||||||
|
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
|
||||||
|
{
|
||||||
|
auto const& txj = jrr[jss::queue_data][0u];
|
||||||
|
BEAST_EXPECT(txj[jss::account] == alice.human());
|
||||||
|
BEAST_EXPECT(txj[jss::fee_level] == "256");
|
||||||
|
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
|
||||||
|
BEAST_EXPECT(txj["retries_remaining"] == 9);
|
||||||
|
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
|
||||||
|
BEAST_EXPECT(txj.isMember(jss::tx));
|
||||||
|
BEAST_EXPECT(txj[jss::tx] == txid1);
|
||||||
|
}
|
||||||
|
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
jv[jss::expand] = true;
|
||||||
|
jv[jss::binary] = true;
|
||||||
|
|
||||||
|
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
|
||||||
|
{
|
||||||
|
auto const& txj = jrr[jss::queue_data][0u];
|
||||||
|
BEAST_EXPECT(txj[jss::account] == alice.human());
|
||||||
|
BEAST_EXPECT(txj[jss::fee_level] == "256");
|
||||||
|
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
|
||||||
|
BEAST_EXPECT(txj["retries_remaining"] == 8);
|
||||||
|
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
|
||||||
|
BEAST_EXPECT(txj.isMember(jss::tx));
|
||||||
|
BEAST_EXPECT(txj[jss::tx].isMember(jss::tx_blob));
|
||||||
|
|
||||||
|
auto const& txj2 = jrr[jss::queue_data][1u];
|
||||||
|
BEAST_EXPECT(txj2[jss::account] == alice.human());
|
||||||
|
BEAST_EXPECT(txj2[jss::fee_level] == "256");
|
||||||
|
BEAST_EXPECT(txj2["preflight_result"] == "tesSUCCESS");
|
||||||
|
BEAST_EXPECT(txj2["retries_remaining"] == 10);
|
||||||
|
BEAST_EXPECT(! txj2.isMember("last_result"));
|
||||||
|
BEAST_EXPECT(txj2.isMember(jss::tx));
|
||||||
|
BEAST_EXPECT(txj2[jss::tx].isMember(jss::tx_blob));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i != 9; ++i)
|
||||||
|
{
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
jv[jss::expand] = false;
|
||||||
|
jv[jss::binary] = false;
|
||||||
|
|
||||||
|
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
|
||||||
|
{
|
||||||
|
auto const& txj = jrr[jss::queue_data][0u];
|
||||||
|
BEAST_EXPECT(txj[jss::account] == alice.human());
|
||||||
|
BEAST_EXPECT(txj[jss::fee_level] == "256");
|
||||||
|
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
|
||||||
|
BEAST_EXPECT(txj["retries_remaining"] == 1);
|
||||||
|
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
|
||||||
|
BEAST_EXPECT(txj.isMember(jss::tx));
|
||||||
|
BEAST_EXPECT(txj[jss::tx] != txid1);
|
||||||
|
txid2 = txj[jss::tx].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
jv[jss::full] = true;
|
||||||
|
|
||||||
|
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
|
||||||
|
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
|
||||||
|
{
|
||||||
|
auto const& txj = jrr[jss::queue_data][0u];
|
||||||
|
BEAST_EXPECT(txj[jss::account] == alice.human());
|
||||||
|
BEAST_EXPECT(txj[jss::fee_level] == "256");
|
||||||
|
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
|
||||||
|
BEAST_EXPECT(txj["retries_remaining"] == 1);
|
||||||
|
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
|
||||||
|
BEAST_EXPECT(txj.isMember(jss::tx));
|
||||||
|
auto const& tx = txj[jss::tx];
|
||||||
|
BEAST_EXPECT(tx[jss::Account] == alice.human());
|
||||||
|
BEAST_EXPECT(tx[jss::TransactionType] == "AccountSet");
|
||||||
|
BEAST_EXPECT(tx[jss::hash] == txid2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void run ()
|
void run ()
|
||||||
{
|
{
|
||||||
@@ -454,6 +666,8 @@ public:
|
|||||||
testNotFoundAccountRoot();
|
testNotFoundAccountRoot();
|
||||||
testAccountRootFromIndex();
|
testAccountRootFromIndex();
|
||||||
testLookupLedger();
|
testLookupLedger();
|
||||||
|
testNoQueue();
|
||||||
|
testQueue();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user