Tx queue enhancements and RPC info (RIPD-1205, RIPD-1206):

* Account-related queue stats (RIPD-1205). Boolean "queue" parameter to
  account_info only if requesting the open ledger.
* Account for the TxQ when autofilling sequence in sign-and-submit (RIPD-1206)
* Tweak TxQ::accept edge case when choosing which tx to try next.
* Labels for experimental "x_" submit parameters use correct separator.

=== Release Notes ===
==== New features ====

When requesting `account_info` for the open ledger, include the `queue :
true` to get extra information about any queued transactions for this
account. (RIPD-1205).

==== Bug fixes ====

When using sign-and-submit mode to autofill a transaction's sequence
number, the logic will not reuse a sequence number that is in the queue
for this account. (RIPD-1206).

Labels for experimental "x_queue_okay" and "x_assume_tx" parameters to
`sign` and `submit` updated to use correct separator.
This commit is contained in:
Edward Hennis
2016-06-29 19:37:19 -04:00
parent 348e65074e
commit e762d09e7e
10 changed files with 714 additions and 37 deletions

View File

@@ -865,9 +865,8 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
});
}
// Update fee computations.
app_.getTxQ().processValidatedLedger(app_, accum,
app_.getTxQ().processClosedLedger(app_, accum,
roundTime_ > 5s);
accum.apply(*buildLCL);
}

View File

@@ -1409,7 +1409,7 @@ void NetworkOPsImp::switchLastClosedLedger (
// Update fee computations.
// TODO: Needs an open ledger
//app_.getTxQ().processValidatedLedger(app_, *newLCL, true);
//app_.getTxQ().processClosedLedger(app_, *newLCL, true);
// Caller must own master lock
{

View File

@@ -90,6 +90,13 @@ public:
std::uint64_t expFeeLevel; // Estimated fee level to get in next ledger
};
struct AccountTxDetails
{
uint64_t feeLevel;
boost::optional<LedgerIndex const> lastValid;
boost::optional<TxConsequences const> consequences;
};
TxQ(Setup const& setup,
beast::Journal j);
@@ -120,19 +127,33 @@ public:
accept(Application& app, OpenView& view);
/**
A new ledger has been validated. Update and clean up the
queue.
Update stats and clean up the queue in preparation for
the next ledger.
*/
void
processValidatedLedger(Application& app,
processClosedLedger(Application& app,
OpenView const& view, bool timeLeap);
/** Returns fee metrics in reference fee level units.
@returns Uninitialized @ref optional if the FeeEscalation
amendment is not enabled.
*/
boost::optional<Metrics>
getMetrics(Config const& config, OpenView const& view,
std::uint32_t txCountPadding = 0) const;
/** Returns information about the transactions currently
in the queue for the account.
@returns Uninitialized @ref optional if the FeeEscalation
amendment is not enabled, OR if the account has no transactions
in the queue.
*/
boost::optional<std::map<TxSeq, AccountTxDetails>>
getAccountTxs(AccountID const& account, Config const& config,
ReadView const& view) const;
/** Packages up fee metrics for the `fee` RPC command.
*/
Json::Value
@@ -215,7 +236,6 @@ private:
scaleFeeLevel(OpenView const& view, std::uint32_t txCountPadding = 0) const;
};
// Alternate name: MaybeTx
class MaybeTx
{
public:

View File

@@ -356,9 +356,11 @@ TxQ::eraseAndAdvance(TxQ::FeeMultiSet::const_iterator_type candidateIter)
the latter case, continue through the fee queue anyway
to head off potential ordering manipulation problems.
*/
auto feeNextIter = std::next(candidateIter);
bool useAccountNext = accountNextIter != txQAccount.transactions.end() &&
accountNextIter->first == candidateIter->sequence + 1 &&
accountNextIter->second.feeLevel > candidateIter->feeLevel;
(feeNextIter == byFee_.end() ||
accountNextIter->second.feeLevel > feeNextIter->feeLevel);
auto candidateNextIter = byFee_.erase(candidateIter);
txQAccount.transactions.erase(accountIter);
return useAccountNext ?
@@ -917,7 +919,7 @@ TxQ::apply(Application& app, OpenView& view,
*/
void
TxQ::processValidatedLedger(Application& app,
TxQ::processClosedLedger(Application& app,
OpenView const& view, bool timeLeap)
{
auto const allowEscalation =
@@ -1145,6 +1147,38 @@ TxQ::getMetrics(Config const& config, OpenView const& view,
return result;
}
auto
TxQ::getAccountTxs(AccountID const& account, Config const& config,
ReadView const& view) const
-> boost::optional<std::map<TxSeq, AccountTxDetails>>
{
auto const allowEscalation =
(view.rules().enabled(featureFeeEscalation,
config.features));
if (!allowEscalation)
return boost::none;
std::lock_guard<std::mutex> lock(mutex_);
auto accountIter = byAccount_.find(account);
if (accountIter == byAccount_.end() ||
accountIter->second.transactions.empty())
return boost::none;
std::map<TxSeq, AccountTxDetails> result;
for (auto const& tx : accountIter->second.transactions)
{
auto& resultTx = result[tx.first];
resultTx.feeLevel = tx.second.feeLevel;
if(tx.second.lastValid)
resultTx.lastValid.emplace(*tx.second.lastValid);
if(tx.second.consequences)
resultTx.consequences.emplace(*tx.second.consequences);
}
return result;
}
Json::Value
TxQ::doRPC(Application& app) const
{

View File

@@ -403,6 +403,33 @@ public:
env(noop(daria), fee(7000), queued);
env(noop(edgar), fee(7000), queued);
checkMetrics(env, 5, boost::none, 3, 2, 256);
{
auto& txQ = env.app().getTxQ();
auto aliceStat = txQ.getAccountTxs(alice.id(),
env.app().config(), *env.current());
if (BEAST_EXPECT(aliceStat))
{
BEAST_EXPECT(aliceStat->size() == 1);
BEAST_EXPECT(aliceStat->begin()->second.feeLevel == 256);
BEAST_EXPECT(aliceStat->begin()->second.lastValid &&
*aliceStat->begin()->second.lastValid == 8);
BEAST_EXPECT(!aliceStat->begin()->second.consequences);
}
auto bobStat = txQ.getAccountTxs(bob.id(),
env.app().config(), *env.current());
if (BEAST_EXPECT(bobStat))
{
BEAST_EXPECT(bobStat->size() == 1);
BEAST_EXPECT(bobStat->begin()->second.feeLevel = 7000 * 256 / 10);
BEAST_EXPECT(!bobStat->begin()->second.lastValid);
BEAST_EXPECT(!bobStat->begin()->second.consequences);
}
auto noStat = txQ.getAccountTxs(Account::master.id(),
env.app().config(), *env.current());
BEAST_EXPECT(!noStat);
}
env.close();
checkMetrics(env, 1, 6, 4, 3, 256);
@@ -719,6 +746,30 @@ public:
++aliceSeq;
}
checkMetrics(env, 8, 8, 5, 4, 257);
{
auto& txQ = env.app().getTxQ();
auto aliceStat = txQ.getAccountTxs(alice.id(),
env.app().config(), *env.current());
std::int64_t fee = 10;
auto seq = env.seq(alice);
if (BEAST_EXPECT(aliceStat))
{
BEAST_EXPECT(aliceStat->size() == 7);
for (auto const& tx : *aliceStat)
{
BEAST_EXPECT(tx.first == seq);
BEAST_EXPECT(tx.second.feeLevel == mulDiv(fee, 256, 10).second);
BEAST_EXPECT(tx.second.lastValid);
BEAST_EXPECT((tx.second.consequences &&
tx.second.consequences->fee == drops(fee) &&
tx.second.consequences->potentialSpend == drops(0) &&
tx.second.consequences->category == TxConsequences::normal) ||
tx.first == env.seq(alice) + 6);
++seq;
fee = (fee + 1) * 125 / 100;
}
}
}
// Alice attempts to add another item to the queue,
// but you can't force your own earlier txn off the
@@ -1557,11 +1608,47 @@ public:
!RPC::contains_error(fee[jss::result])))
{
auto const& result = fee[jss::result];
BEAST_EXPECT(result.isMember(jss::drops));
BEAST_EXPECT(result.isMember(jss::levels));
BEAST_EXPECT(result.isMember(jss::current_ledger_size));
BEAST_EXPECT(result.isMember(jss::current_queue_size));
BEAST_EXPECT(result.isMember(jss::expected_ledger_size));
BEAST_EXPECT(!result.isMember(jss::max_queue_size));
BEAST_EXPECT(result.isMember(jss::drops));
auto const& drops = result[jss::drops];
BEAST_EXPECT(drops.isMember(jss::base_fee));
BEAST_EXPECT(drops.isMember(jss::median_fee));
BEAST_EXPECT(drops.isMember(jss::minimum_fee));
BEAST_EXPECT(drops.isMember(jss::open_ledger_fee));
BEAST_EXPECT(result.isMember(jss::levels));
auto const& levels = result[jss::levels];
BEAST_EXPECT(levels.isMember(jss::median_level));
BEAST_EXPECT(levels.isMember(jss::minimum_level));
BEAST_EXPECT(levels.isMember(jss::open_ledger_level));
BEAST_EXPECT(levels.isMember(jss::reference_level));
}
env.close();
fee = env.rpc("fee");
if (BEAST_EXPECT(fee.isMember(jss::result) &&
!RPC::contains_error(fee[jss::result])))
{
auto const& result = fee[jss::result];
BEAST_EXPECT(result.isMember(jss::current_ledger_size));
BEAST_EXPECT(result.isMember(jss::current_queue_size));
BEAST_EXPECT(result.isMember(jss::expected_ledger_size));
BEAST_EXPECT(result.isMember(jss::max_queue_size));
auto const& drops = result[jss::drops];
BEAST_EXPECT(drops.isMember(jss::base_fee));
BEAST_EXPECT(drops.isMember(jss::median_fee));
BEAST_EXPECT(drops.isMember(jss::minimum_fee));
BEAST_EXPECT(drops.isMember(jss::open_ledger_fee));
BEAST_EXPECT(result.isMember(jss::levels));
auto const& levels = result[jss::levels];
BEAST_EXPECT(levels.isMember(jss::median_level));
BEAST_EXPECT(levels.isMember(jss::minimum_level));
BEAST_EXPECT(levels.isMember(jss::open_ledger_level));
BEAST_EXPECT(levels.isMember(jss::reference_level));
}
}
@@ -1668,6 +1755,422 @@ public:
BEAST_EXPECT(env.seq(alice) == aliceSeq + 4);
}
void testSignAndSubmitSequence()
{
testcase("Autofilled sequence should account for TxQ");
using namespace jtx;
Env env(*this, makeConfig({ {"minimum_txn_in_ledger_standalone", "6"} }),
features(featureFeeEscalation));
Env_ss envs(env);
auto const& txQ = env.app().getTxQ();
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(100000), alice, bob);
auto params = Json::Value(Json::objectValue);
// Max fee = 50k drops
params[jss::fee_mult_max] = 100;
params["x_queue_okay"] = true;
fillQueue(env, alice);
checkMetrics(env, 0, boost::none, 7, 6, 256);
// Queue up several transactions for alice sign-and-submit
auto const aliceSeq = env.seq(alice);
auto const lastLedgerSeq = env.current()->info().seq + 2;
for (int i = 0; i < 5; ++i)
{
if (i == 2)
envs(noop(alice), fee(none), seq(none),
json(jss::LastLedgerSequence, lastLedgerSeq),
ter(terQUEUED))(params);
else
envs(noop(alice), fee(none), seq(none),
ter(terQUEUED))(params);
}
checkMetrics(env, 5, boost::none, 7, 6, 256);
{
auto aliceStat = txQ.getAccountTxs(alice.id(),
env.app().config(), *env.current());
if (BEAST_EXPECT(aliceStat))
{
auto seq = aliceSeq;
BEAST_EXPECT(aliceStat->size() == 5);
for (auto const& tx : *aliceStat)
{
BEAST_EXPECT(tx.first == seq);
BEAST_EXPECT(tx.second.feeLevel == 25600);
if(seq == aliceSeq + 2)
{
BEAST_EXPECT(tx.second.lastValid &&
*tx.second.lastValid == lastLedgerSeq);
}
else
{
BEAST_EXPECT(!tx.second.lastValid);
}
++seq;
}
}
}
// Put some txs in the queue for bob.
// Give them a higher fee so they'll beat alice's.
for (int i = 0; i < 8; ++i)
envs(noop(bob), fee(2000), seq(none), ter(terQUEUED))();
checkMetrics(env, 13, boost::none, 7, 6, 256);
env.close();
checkMetrics(env, 5, 14, 8, 7, 256);
// Put some more txs in the queue for bob.
// Give them a higher fee so they'll beat alice's.
fillQueue(env, bob);
for(int i = 0; i < 9; ++i)
envs(noop(bob), fee(2000), seq(none), ter(terQUEUED))();
checkMetrics(env, 14, 14, 8, 7, 25601);
env.close();
// Put some more txs in the queue for bob.
// Give them a higher fee so they'll beat alice's.
fillQueue(env, bob);
for (int i = 0; i < 10; ++i)
envs(noop(bob), fee(2000), seq(none), ter(terQUEUED))();
checkMetrics(env, 15, 16, 9, 8, 256);
env.close();
checkMetrics(env, 4, 18, 10, 9, 256);
{
// Bob has nothing left in the queue.
auto bobStat = txQ.getAccountTxs(bob.id(),
env.app().config(), *env.current());
BEAST_EXPECT(!bobStat);
}
// Verify alice's tx got dropped as we BEAST_EXPECT, and that there's
// a gap in her queued txs.
{
auto aliceStat = txQ.getAccountTxs(alice.id(),
env.app().config(), *env.current());
if (BEAST_EXPECT(aliceStat))
{
auto seq = aliceSeq;
BEAST_EXPECT(aliceStat->size() == 4);
for (auto const& tx : *aliceStat)
{
// Skip over the missing one.
if (seq == aliceSeq + 2)
++seq;
BEAST_EXPECT(tx.first == seq);
BEAST_EXPECT(tx.second.feeLevel == 25600);
BEAST_EXPECT(!tx.second.lastValid);
++seq;
}
}
}
// Now, fill the gap.
envs(noop(alice), fee(none), seq(none), ter(terQUEUED))(params);
checkMetrics(env, 5, 18, 10, 9, 256);
{
auto aliceStat = txQ.getAccountTxs(alice.id(),
env.app().config(), *env.current());
if (BEAST_EXPECT(aliceStat))
{
auto seq = aliceSeq;
BEAST_EXPECT(aliceStat->size() == 5);
for (auto const& tx : *aliceStat)
{
BEAST_EXPECT(tx.first == seq);
BEAST_EXPECT(tx.second.feeLevel == 25600);
BEAST_EXPECT(!tx.second.lastValid);
++seq;
}
}
}
env.close();
checkMetrics(env, 0, 20, 5, 10, 256);
{
// Bob's data has been cleaned up.
auto bobStat = txQ.getAccountTxs(bob.id(),
env.app().config(), *env.current());
BEAST_EXPECT(!bobStat);
}
{
auto aliceStat = txQ.getAccountTxs(alice.id(),
env.app().config(), *env.current());
BEAST_EXPECT(!aliceStat);
}
}
void testAccountInfo()
{
using namespace jtx;
Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }),
features(featureFeeEscalation));
Env_ss envs(env);
Account const alice{ "alice" };
env.fund(XRP(1000000), alice);
env.close();
auto const withQueue =
R"({ "account": ")" + alice.human() +
R"(", "queue": true })";
auto const withoutQueue =
R"({ "account": ")" + alice.human() +
R"("})";
auto const prevLedgerWithQueue =
R"({ "account": ")" + alice.human() +
R"(", "queue": true, "ledger_index": 3 })";
BEAST_EXPECT(env.current()->info().seq > 3);
auto submitParams = Json::Value(Json::objectValue);
// Max fee = 100 drops
submitParams[jss::fee_mult_max] = 10;
submitParams["x_queue_okay"] = true;
{
// account_info without the "queue" argument.
auto const info = env.rpc("json", "account_info", withoutQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
BEAST_EXPECT(!info[jss::result].isMember(jss::queue_data));
}
{
// account_info with the "queue" argument.
auto const info = env.rpc("json", "account_info", withQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& result = info[jss::result];
BEAST_EXPECT(result.isMember(jss::queue_data));
auto const& queue_data = result[jss::queue_data];
BEAST_EXPECT(queue_data.isObject());
BEAST_EXPECT(queue_data.isMember(jss::txn_count));
BEAST_EXPECT(queue_data[jss::txn_count] == 0);
BEAST_EXPECT(!queue_data.isMember(jss::lowest_sequence));
BEAST_EXPECT(!queue_data.isMember(jss::highest_sequence));
BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
BEAST_EXPECT(!queue_data.isMember(jss::transactions));
}
checkMetrics(env, 0, 6, 0, 3, 256);
fillQueue(env, alice);
checkMetrics(env, 0, 6, 4, 3, 256);
{
auto const info = env.rpc("json", "account_info", withQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& result = info[jss::result];
BEAST_EXPECT(result.isMember(jss::queue_data));
auto const& queue_data = result[jss::queue_data];
BEAST_EXPECT(queue_data.isObject());
BEAST_EXPECT(queue_data.isMember(jss::txn_count));
BEAST_EXPECT(queue_data[jss::txn_count] == 0);
BEAST_EXPECT(!queue_data.isMember(jss::lowest_sequence));
BEAST_EXPECT(!queue_data.isMember(jss::highest_sequence));
BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
BEAST_EXPECT(!queue_data.isMember(jss::transactions));
}
envs(noop(alice), fee(none), seq(none), ter(terQUEUED))(submitParams);
envs(noop(alice), fee(none), seq(none), ter(terQUEUED))(submitParams);
envs(noop(alice), fee(none), seq(none), ter(terQUEUED))(submitParams);
envs(noop(alice), fee(none), seq(none), ter(terQUEUED))(submitParams);
checkMetrics(env, 4, 6, 4, 3, 256);
{
auto const info = env.rpc("json", "account_info", withQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& result = info[jss::result];
auto const& data = result[jss::account_data];
BEAST_EXPECT(result.isMember(jss::queue_data));
auto const& queue_data = result[jss::queue_data];
BEAST_EXPECT(queue_data.isObject());
BEAST_EXPECT(queue_data.isMember(jss::txn_count));
BEAST_EXPECT(queue_data[jss::txn_count] == 4);
BEAST_EXPECT(queue_data.isMember(jss::lowest_sequence));
BEAST_EXPECT(queue_data[jss::lowest_sequence] == data[jss::Sequence]);
BEAST_EXPECT(queue_data.isMember(jss::highest_sequence));
BEAST_EXPECT(queue_data[jss::highest_sequence] ==
data[jss::Sequence].asUInt() +
queue_data[jss::txn_count].asUInt() - 1);
BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
BEAST_EXPECT(queue_data.isMember(jss::transactions));
auto const& queued = queue_data[jss::transactions];
BEAST_EXPECT(queued.size() == queue_data[jss::txn_count]);
for (unsigned i = 0; i < queued.size(); ++i)
{
auto const& item = queued[i];
BEAST_EXPECT(item[jss::seq] == data[jss::Sequence].asInt() + i);
BEAST_EXPECT(item[jss::fee_level] == "2560");
BEAST_EXPECT(!item.isMember(jss::LastLedgerSequence));
if (i == queued.size() - 1)
{
BEAST_EXPECT(!item.isMember(jss::fee));
BEAST_EXPECT(!item.isMember(jss::max_spend_drops));
BEAST_EXPECT(!item.isMember(jss::auth_change));
}
else
{
BEAST_EXPECT(item.isMember(jss::fee));
BEAST_EXPECT(item[jss::fee] == "100");
BEAST_EXPECT(item.isMember(jss::max_spend_drops));
BEAST_EXPECT(item[jss::max_spend_drops] == "100");
BEAST_EXPECT(item.isMember(jss::auth_change));
BEAST_EXPECT(!item[jss::auth_change].asBool());
}
}
}
// Queue up a blocker
envs(fset(alice, asfAccountTxnID), fee(none), seq(none),
json(jss::LastLedgerSequence, 10),
ter(terQUEUED))(submitParams);
checkMetrics(env, 5, 6, 4, 3, 256);
{
auto const info = env.rpc("json", "account_info", withQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& result = info[jss::result];
auto const& data = result[jss::account_data];
BEAST_EXPECT(result.isMember(jss::queue_data));
auto const& queue_data = result[jss::queue_data];
BEAST_EXPECT(queue_data.isObject());
BEAST_EXPECT(queue_data.isMember(jss::txn_count));
BEAST_EXPECT(queue_data[jss::txn_count] == 5);
BEAST_EXPECT(queue_data.isMember(jss::lowest_sequence));
BEAST_EXPECT(queue_data[jss::lowest_sequence] == data[jss::Sequence]);
BEAST_EXPECT(queue_data.isMember(jss::highest_sequence));
BEAST_EXPECT(queue_data[jss::highest_sequence] ==
data[jss::Sequence].asUInt() +
queue_data[jss::txn_count].asUInt() - 1);
BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
BEAST_EXPECT(queue_data.isMember(jss::transactions));
auto const& queued = queue_data[jss::transactions];
BEAST_EXPECT(queued.size() == queue_data[jss::txn_count]);
for (unsigned i = 0; i < queued.size(); ++i)
{
auto const& item = queued[i];
BEAST_EXPECT(item[jss::seq] == data[jss::Sequence].asInt() + i);
BEAST_EXPECT(item[jss::fee_level] == "2560");
if (i == queued.size() - 1)
{
BEAST_EXPECT(!item.isMember(jss::fee));
BEAST_EXPECT(!item.isMember(jss::max_spend_drops));
BEAST_EXPECT(!item.isMember(jss::auth_change));
BEAST_EXPECT(item.isMember(jss::LastLedgerSequence));
BEAST_EXPECT(item[jss::LastLedgerSequence] == 10);
}
else
{
BEAST_EXPECT(item.isMember(jss::fee));
BEAST_EXPECT(item[jss::fee] == "100");
BEAST_EXPECT(item.isMember(jss::max_spend_drops));
BEAST_EXPECT(item[jss::max_spend_drops] == "100");
BEAST_EXPECT(item.isMember(jss::auth_change));
BEAST_EXPECT(!item[jss::auth_change].asBool());
BEAST_EXPECT(!item.isMember(jss::LastLedgerSequence));
}
}
}
envs(noop(alice), fee(none), seq(none), ter(telCAN_NOT_QUEUE))(submitParams);
checkMetrics(env, 5, 6, 4, 3, 256);
{
auto const info = env.rpc("json", "account_info", withQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& result = info[jss::result];
auto const& data = result[jss::account_data];
BEAST_EXPECT(result.isMember(jss::queue_data));
auto const& queue_data = result[jss::queue_data];
BEAST_EXPECT(queue_data.isObject());
BEAST_EXPECT(queue_data.isMember(jss::txn_count));
BEAST_EXPECT(queue_data[jss::txn_count] == 5);
BEAST_EXPECT(queue_data.isMember(jss::lowest_sequence));
BEAST_EXPECT(queue_data[jss::lowest_sequence] == data[jss::Sequence]);
BEAST_EXPECT(queue_data.isMember(jss::highest_sequence));
BEAST_EXPECT(queue_data[jss::highest_sequence] ==
data[jss::Sequence].asUInt() +
queue_data[jss::txn_count].asUInt() - 1);
BEAST_EXPECT(queue_data.isMember(jss::auth_change_queued));
BEAST_EXPECT(queue_data[jss::auth_change_queued].asBool());
BEAST_EXPECT(queue_data.isMember(jss::max_spend_drops_total));
BEAST_EXPECT(queue_data[jss::max_spend_drops_total] == "500");
BEAST_EXPECT(queue_data.isMember(jss::transactions));
auto const& queued = queue_data[jss::transactions];
BEAST_EXPECT(queued.size() == queue_data[jss::txn_count]);
for (unsigned i = 0; i < queued.size(); ++i)
{
auto const& item = queued[i];
BEAST_EXPECT(item[jss::seq] == data[jss::Sequence].asInt() + i);
BEAST_EXPECT(item[jss::fee_level] == "2560");
if (i == queued.size() - 1)
{
BEAST_EXPECT(item.isMember(jss::fee));
BEAST_EXPECT(item[jss::fee] == "100");
BEAST_EXPECT(item.isMember(jss::max_spend_drops));
BEAST_EXPECT(item[jss::max_spend_drops] == "100");
BEAST_EXPECT(item.isMember(jss::auth_change));
BEAST_EXPECT(item[jss::auth_change].asBool());
BEAST_EXPECT(item.isMember(jss::LastLedgerSequence));
BEAST_EXPECT(item[jss::LastLedgerSequence] == 10);
}
else
{
BEAST_EXPECT(item.isMember(jss::fee));
BEAST_EXPECT(item[jss::fee] == "100");
BEAST_EXPECT(item.isMember(jss::max_spend_drops));
BEAST_EXPECT(item[jss::max_spend_drops] == "100");
BEAST_EXPECT(item.isMember(jss::auth_change));
BEAST_EXPECT(!item[jss::auth_change].asBool());
BEAST_EXPECT(!item.isMember(jss::LastLedgerSequence));
}
}
}
{
auto const info = env.rpc("json", "account_info", prevLedgerWithQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
RPC::contains_error(info[jss::result]));
}
env.close();
checkMetrics(env, 1, 8, 5, 4, 256);
env.close();
checkMetrics(env, 0, 10, 1, 5, 256);
{
auto const info = env.rpc("json", "account_info", withQueue);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& result = info[jss::result];
BEAST_EXPECT(result.isMember(jss::queue_data));
auto const& queue_data = result[jss::queue_data];
BEAST_EXPECT(queue_data.isObject());
BEAST_EXPECT(queue_data.isMember(jss::txn_count));
BEAST_EXPECT(queue_data[jss::txn_count] == 0);
BEAST_EXPECT(!queue_data.isMember(jss::lowest_sequence));
BEAST_EXPECT(!queue_data.isMember(jss::highest_sequence));
BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
BEAST_EXPECT(!queue_data.isMember(jss::transactions));
}
}
void run()
{
testQueue();
@@ -1687,6 +2190,8 @@ public:
testConsequences();
testRPC();
testExpirationReplacement();
testSignAndSubmitSequence();
testAccountInfo();
}
};

View File

@@ -83,6 +83,8 @@ JSS ( amendments ); // in: AccountObjects, out: NetworkOPs
JSS ( asks ); // out: Subscribe
JSS ( assets ); // out: GatewayBalances
JSS ( authorized ); // out: AccountLines
JSS ( auth_change ); // out: AccountInfo
JSS ( auth_change_queued ); // out: AccountInfo
JSS ( balance ); // out: AccountLines
JSS ( balances ); // out: GatewayBalances
JSS ( base ); // out: LogLevel
@@ -162,6 +164,7 @@ JSS ( features ); // out: Feature
JSS ( fee ); // out: NetworkOPs, Peers
JSS ( fee_base ); // out: NetworkOPs
JSS ( fee_div_max ); // in: TransactionSign
JSS ( fee_level ); // out: AccountInfo
JSS ( fee_mult_max ); // in: TransactionSign
JSS ( fee_ref ); // out: NetworkOPs
JSS ( fetch_pack ); // out: NetworkOPs
@@ -184,6 +187,7 @@ JSS ( hashes ); // in: AccountObjects
JSS ( have_header ); // out: InboundLedger
JSS ( have_state ); // out: InboundLedger
JSS ( have_transactions ); // out: InboundLedger
JSS ( highest_sequence ); // out: AccountInfo
JSS ( hostid ); // out: NetworkOPs
JSS ( hotwallet ); // in: GatewayBalances
JSS ( id ); // websocket.
@@ -243,6 +247,7 @@ JSS ( load_factor_net ); // out: NetworkOPs
JSS ( load_fee ); // out: LoadFeeTrackImp, NetworkOPs
JSS ( local ); // out: resource/Logic.h
JSS ( local_txs ); // out: GetCounts
JSS ( lowest_sequence ); // out: AccountInfo
JSS ( majority ); // out: RPC feature
JSS ( marker ); // in/out: AccountTx, AccountOffers,
// AccountLines, AccountObjects,
@@ -253,6 +258,8 @@ JSS ( master_seed ); // out: WalletPropose
JSS ( master_seed_hex ); // out: WalletPropose
JSS ( max_ledger ); // in/out: LedgerCleaner
JSS ( max_queue_size ); // out: TxQ
JSS ( max_spend_drops ); // out: AccountInfo
JSS ( max_spend_drops_total ); // out: AccountInfo
JSS ( median_fee ); // out: TxQ
JSS ( median_level ); // out: TxQ
JSS ( message ); // error.
@@ -317,6 +324,8 @@ JSS ( published_ledger ); // out: NetworkOPs
JSS ( quality ); // out: NetworkOPs
JSS ( quality_in ); // out: AccountLines
JSS ( quality_out ); // out: AccountLines
JSS ( queue ); // in: AccountInfo
JSS ( queue_data ); // out: AccountInfo
JSS ( random ); // out: Random
JSS ( raw_meta ); // out: AcceptedLedgerTx
JSS ( receive_currencies ); // out: AccountCurrencies

View File

@@ -20,6 +20,7 @@
#include <BeastConfig.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/json/json_value.h>
#include <ripple/ledger/ReadView.h>
#include <ripple/protocol/ErrorCodes.h>
@@ -39,6 +40,11 @@ namespace ripple {
// ledger_index : <ledger_index>
// signer_lists : <bool> // optional (default false)
// // if true return SignerList(s).
// queue : <bool> // optional (default false)
// // if true return information about transactions
// // in the current TxQ, only if the requested
// // ledger is open. Otherwise if true, returns an
// // error.
// }
// TODO(tom): what is that "default"?
@@ -46,17 +52,20 @@ Json::Value doAccountInfo (RPC::Context& context)
{
auto& params = context.params;
std::string strIdent;
if (params.isMember(jss::account))
strIdent = params[jss::account].asString();
else if (params.isMember(jss::ident))
strIdent = params[jss::ident].asString();
else
return RPC::missing_field_error (jss::account);
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger (ledger, context);
if (!ledger)
return result;
if (!params.isMember (jss::account) && !params.isMember (jss::ident))
return RPC::missing_field_error (jss::account);
std::string strIdent = params.isMember (jss::account)
? params[jss::account].asString () : params[jss::ident].asString ();
bool bStrict = params.isMember (jss::strict) && params[jss::strict].asBool ();
AccountID accountID;
@@ -70,6 +79,17 @@ Json::Value doAccountInfo (RPC::Context& context)
auto const sleAccepted = ledger->read(keylet::account(accountID));
if (sleAccepted)
{
auto const queue = params.isMember(jss::queue) &&
params[jss::queue].asBool();
if (queue && !ledger->open())
{
// It doesn't make sense to request the queue
// with any closed or validated ledger.
RPC::inject_error(rpcINVALID_PARAMS, result);
return result;
}
RPC::injectSLE(jvAccepted, *sleAccepted);
result[jss::account_data] = jvAccepted;
@@ -79,14 +99,80 @@ Json::Value doAccountInfo (RPC::Context& context)
{
// We put the SignerList in an array because of an anticipated
// future when we support multiple signer lists on one account.
auto& jvSignerList = result[jss::account_data][jss::signer_lists];
jvSignerList = Json::arrayValue;
Json::Value jvSignerList = Json::arrayValue;
// This code will need to be revisited if in the future we support
// multiple SignerLists on one account.
auto const sleSigners = ledger->read (keylet::signers (accountID));
if (sleSigners)
jvSignerList.append (sleSigners->getJson (0));
result[jss::account_data][jss::signer_lists] =
std::move(jvSignerList);
}
// Return queue info if that is requested
if (queue)
{
Json::Value jvQueueData = Json::objectValue;
auto const txs = context.app.getTxQ().getAccountTxs(
accountID, context.app.config(), *ledger);
if (txs && !txs->empty())
{
jvQueueData[jss::txn_count] = static_cast<Json::UInt>(txs->size());
jvQueueData[jss::lowest_sequence] = txs->begin()->first;
jvQueueData[jss::highest_sequence] = txs->rbegin()->first;
auto& jvQueueTx = jvQueueData[jss::transactions];
jvQueueTx = Json::arrayValue;
boost::optional<bool> anyAuthChanged(false);
boost::optional<XRPAmount> totalSpend(0);
for (auto const& tx : *txs)
{
Json::Value jvTx = Json::objectValue;
jvTx[jss::seq] = tx.first;
jvTx[jss::fee_level] = to_string(tx.second.feeLevel);
if (tx.second.lastValid)
jvTx[jss::LastLedgerSequence] = *tx.second.lastValid;
if (tx.second.consequences)
{
jvTx[jss::fee] = to_string(
tx.second.consequences->fee);
auto spend = tx.second.consequences->potentialSpend +
tx.second.consequences->fee;
jvTx[jss::max_spend_drops] = to_string(spend);
if (totalSpend)
*totalSpend += spend;
auto authChanged = tx.second.consequences->category ==
TxConsequences::blocker;
if (authChanged)
anyAuthChanged.emplace(authChanged);
jvTx[jss::auth_change] = authChanged;
}
else
{
if (anyAuthChanged && !*anyAuthChanged)
anyAuthChanged.reset();
totalSpend.reset();
}
jvQueueTx.append(std::move(jvTx));
}
if (anyAuthChanged)
jvQueueData[jss::auth_change_queued] =
*anyAuthChanged;
if (totalSpend)
jvQueueData[jss::max_spend_drops_total] =
to_string(*totalSpend);
}
else
jvQueueData[jss::txn_count] = 0u;
result[jss::queue_data] = std::move(jvQueueData);
}
}
else

View File

@@ -428,7 +428,21 @@ transactionPreProcessImpl (
return rpcError (rpcSRC_ACT_NOT_FOUND);
}
tx_json[jss::Sequence] = (*sle)[sfSequence];
auto seq = (*sle)[sfSequence];
auto const queued = app.getTxQ().getAccountTxs(srcAddressID,
app.config(), *ledger);
// If the account has any txs in the TxQ, skip those sequence
// numbers (accounting for possible gaps).
if(queued)
for(auto const& tx : *queued)
{
if (tx.first == seq)
++seq;
else if (tx.first > seq)
break;
}
tx_json[jss::Sequence] = seq;
}
if (!tx_json.isMember (jss::Flags))
@@ -680,9 +694,9 @@ Json::Value checkFee (
ledger->fees().base, ledger->fees().units, isUnlimited (role));
std::uint64_t fee = loadFee;
{
auto const assumeTx = request.isMember("x-assume-tx") &&
request["x-assume-tx"].isConvertibleTo(Json::uintValue) ?
request["x-assume-tx"].asUInt() : 0;
auto const assumeTx = request.isMember("x_assume_tx") &&
request["x_assume_tx"].isConvertibleTo(Json::uintValue) ?
request["x_assume_tx"].asUInt() : 0;
auto const metrics = txQ.getMetrics(config, *ledger, assumeTx);
if(metrics)
{
@@ -702,9 +716,9 @@ Json::Value checkFee (
mult, div);
if (fee > limit && fee != loadFee &&
request.isMember("x-queue-okay") &&
request["x-queue-okay"].isBool() &&
request["x-queue-okay"].asBool())
request.isMember("x_queue_okay") &&
request["x_queue_okay"].isBool() &&
request["x_queue_okay"].asBool())
{
fee = std::max(loadFee, limit);
}

View File

@@ -74,12 +74,16 @@ public:
{
// account_info without the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withoutSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
BEAST_EXPECT(! info[jss::result][jss::account_data].
isMember (jss::signer_lists));
}
{
// account_info with the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& data = info[jss::result][jss::account_data];
BEAST_EXPECT(data.isMember (jss::signer_lists));
auto const& signerLists = data[jss::signer_lists];
@@ -95,12 +99,16 @@ public:
{
// account_info without the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withoutSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
BEAST_EXPECT(! info[jss::result][jss::account_data].
isMember (jss::signer_lists));
}
{
// account_info with the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& data = info[jss::result][jss::account_data];
BEAST_EXPECT(data.isMember (jss::signer_lists));
auto const& signerLists = data[jss::signer_lists];
@@ -131,6 +139,8 @@ public:
{
// account_info with the "signer_lists" argument.
auto const info = env.rpc ("json", "account_info", withSigners);
BEAST_EXPECT(info.isMember(jss::result) &&
info[jss::result].isMember(jss::account_data));
auto const& data = info[jss::result][jss::account_data];
BEAST_EXPECT(data.isMember (jss::signer_lists));
auto const& signerLists = data[jss::signer_lists];

View File

@@ -1950,7 +1950,7 @@ public:
Json::Value req;
Json::Reader ().parse (R"({
"fee_mult_max" : 1000,
"x-queue-okay" : false,
"x_queue_okay" : false,
"tx_json" : { }
})", req);
Json::Value result =
@@ -1968,7 +1968,7 @@ public:
Json::Value req;
Json::Reader().parse(R"({
"fee_mult_max" : 1000,
"x-queue-okay" : true,
"x_queue_okay" : true,
"tx_json" : { }
})", req);
Json::Value result =
@@ -1986,7 +1986,7 @@ public:
Json::Value req;
Json::Reader().parse(R"({
"fee_mult_max" : 1000,
"x-assume-tx" : 4,
"x_assume_tx" : 4,
"tx_json" : { }
})", req);
Json::Value result =
@@ -2004,8 +2004,8 @@ public:
Json::Value req;
Json::Reader().parse(R"({
"fee_mult_max" : 1000,
"x-assume-tx" : 4,
"x-queue-okay" : true,
"x_assume_tx" : 4,
"x_queue_okay" : true,
"tx_json" : { }
})", req);
Json::Value result =
@@ -2041,7 +2041,7 @@ public:
Json::Value req;
Json::Reader().parse(R"({
"fee_mult_max" : 5,
"x-queue-okay" : true,
"x_queue_okay" : true,
"tx_json" : { }
})", req);
Json::Value result =
@@ -2059,8 +2059,8 @@ public:
Json::Value req;
Json::Reader().parse(R"({
"fee_mult_max" : 5,
"x-assume-tx" : 4,
"x-queue-okay" : false,
"x_assume_tx" : 4,
"x_queue_okay" : false,
"tx_json" : { }
})", req);
Json::Value result =
@@ -2077,8 +2077,8 @@ public:
Json::Value req;
Json::Reader().parse(R"({
"fee_mult_max" : 5,
"x-assume-tx" : 4,
"x-queue-okay" : true,
"x_assume_tx" : 4,
"x_queue_okay" : true,
"tx_json" : { }
})", req);
Json::Value result =
@@ -2097,8 +2097,8 @@ public:
Json::Reader().parse(R"({
"fee_mult_max" : 1000,
"fee_div_max" : 3,
"x-assume-tx" : 4,
"x-queue-okay" : true,
"x_assume_tx" : 4,
"x_queue_okay" : true,
"tx_json" : { }
})", req);
Json::Value result =