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. // Update fee computations.
app_.getTxQ().processValidatedLedger(app_, accum, app_.getTxQ().processClosedLedger(app_, accum,
roundTime_ > 5s); roundTime_ > 5s);
accum.apply(*buildLCL); accum.apply(*buildLCL);
} }

View File

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

View File

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

View File

@@ -356,9 +356,11 @@ TxQ::eraseAndAdvance(TxQ::FeeMultiSet::const_iterator_type candidateIter)
the latter case, continue through the fee queue anyway the latter case, continue through the fee queue anyway
to head off potential ordering manipulation problems. to head off potential ordering manipulation problems.
*/ */
auto feeNextIter = std::next(candidateIter);
bool useAccountNext = accountNextIter != txQAccount.transactions.end() && bool useAccountNext = accountNextIter != txQAccount.transactions.end() &&
accountNextIter->first == candidateIter->sequence + 1 && accountNextIter->first == candidateIter->sequence + 1 &&
accountNextIter->second.feeLevel > candidateIter->feeLevel; (feeNextIter == byFee_.end() ||
accountNextIter->second.feeLevel > feeNextIter->feeLevel);
auto candidateNextIter = byFee_.erase(candidateIter); auto candidateNextIter = byFee_.erase(candidateIter);
txQAccount.transactions.erase(accountIter); txQAccount.transactions.erase(accountIter);
return useAccountNext ? return useAccountNext ?
@@ -917,7 +919,7 @@ TxQ::apply(Application& app, OpenView& view,
*/ */
void void
TxQ::processValidatedLedger(Application& app, TxQ::processClosedLedger(Application& app,
OpenView const& view, bool timeLeap) OpenView const& view, bool timeLeap)
{ {
auto const allowEscalation = auto const allowEscalation =
@@ -1145,6 +1147,38 @@ TxQ::getMetrics(Config const& config, OpenView const& view,
return result; 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 Json::Value
TxQ::doRPC(Application& app) const TxQ::doRPC(Application& app) const
{ {

View File

@@ -403,6 +403,33 @@ public:
env(noop(daria), fee(7000), queued); env(noop(daria), fee(7000), queued);
env(noop(edgar), fee(7000), queued); env(noop(edgar), fee(7000), queued);
checkMetrics(env, 5, boost::none, 3, 2, 256); 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(); env.close();
checkMetrics(env, 1, 6, 4, 3, 256); checkMetrics(env, 1, 6, 4, 3, 256);
@@ -719,6 +746,30 @@ public:
++aliceSeq; ++aliceSeq;
} }
checkMetrics(env, 8, 8, 5, 4, 257); 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, // 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
@@ -1557,11 +1608,47 @@ public:
!RPC::contains_error(fee[jss::result]))) !RPC::contains_error(fee[jss::result])))
{ {
auto const& result = 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_ledger_size));
BEAST_EXPECT(result.isMember(jss::current_queue_size)); BEAST_EXPECT(result.isMember(jss::current_queue_size));
BEAST_EXPECT(result.isMember(jss::expected_ledger_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); 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() void run()
{ {
testQueue(); testQueue();
@@ -1687,6 +2190,8 @@ public:
testConsequences(); testConsequences();
testRPC(); testRPC();
testExpirationReplacement(); testExpirationReplacement();
testSignAndSubmitSequence();
testAccountInfo();
} }
}; };

View File

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

View File

@@ -20,6 +20,7 @@
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/main/Application.h> #include <ripple/app/main/Application.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/json/json_value.h> #include <ripple/json/json_value.h>
#include <ripple/ledger/ReadView.h> #include <ripple/ledger/ReadView.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
@@ -39,6 +40,11 @@ namespace ripple {
// ledger_index : <ledger_index> // ledger_index : <ledger_index>
// signer_lists : <bool> // optional (default false) // signer_lists : <bool> // optional (default false)
// // if true return SignerList(s). // // 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"? // TODO(tom): what is that "default"?
@@ -46,17 +52,20 @@ Json::Value doAccountInfo (RPC::Context& context)
{ {
auto& params = context.params; 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; std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger (ledger, context); auto result = RPC::lookupLedger (ledger, context);
if (!ledger) if (!ledger)
return result; 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 (); bool bStrict = params.isMember (jss::strict) && params[jss::strict].asBool ();
AccountID accountID; AccountID accountID;
@@ -70,6 +79,17 @@ Json::Value doAccountInfo (RPC::Context& context)
auto const sleAccepted = ledger->read(keylet::account(accountID)); auto const sleAccepted = ledger->read(keylet::account(accountID));
if (sleAccepted) 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); RPC::injectSLE(jvAccepted, *sleAccepted);
result[jss::account_data] = jvAccepted; 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 // We put the SignerList in an array because of an anticipated
// future when we support multiple signer lists on one account. // future when we support multiple signer lists on one account.
auto& jvSignerList = result[jss::account_data][jss::signer_lists]; Json::Value jvSignerList = Json::arrayValue;
jvSignerList = Json::arrayValue;
// This code will need to be revisited if in the future we support // This code will need to be revisited if in the future we support
// multiple SignerLists on one account. // multiple SignerLists on one account.
auto const sleSigners = ledger->read (keylet::signers (accountID)); auto const sleSigners = ledger->read (keylet::signers (accountID));
if (sleSigners) if (sleSigners)
jvSignerList.append (sleSigners->getJson (0)); 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 else

View File

@@ -428,7 +428,21 @@ transactionPreProcessImpl (
return rpcError (rpcSRC_ACT_NOT_FOUND); 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)) if (!tx_json.isMember (jss::Flags))
@@ -680,9 +694,9 @@ Json::Value checkFee (
ledger->fees().base, ledger->fees().units, isUnlimited (role)); ledger->fees().base, ledger->fees().units, isUnlimited (role));
std::uint64_t fee = loadFee; std::uint64_t fee = loadFee;
{ {
auto const assumeTx = request.isMember("x-assume-tx") && auto const assumeTx = request.isMember("x_assume_tx") &&
request["x-assume-tx"].isConvertibleTo(Json::uintValue) ? request["x_assume_tx"].isConvertibleTo(Json::uintValue) ?
request["x-assume-tx"].asUInt() : 0; request["x_assume_tx"].asUInt() : 0;
auto const metrics = txQ.getMetrics(config, *ledger, assumeTx); auto const metrics = txQ.getMetrics(config, *ledger, assumeTx);
if(metrics) if(metrics)
{ {
@@ -702,9 +716,9 @@ Json::Value checkFee (
mult, div); mult, div);
if (fee > limit && fee != loadFee && if (fee > limit && fee != loadFee &&
request.isMember("x-queue-okay") && request.isMember("x_queue_okay") &&
request["x-queue-okay"].isBool() && request["x_queue_okay"].isBool() &&
request["x-queue-okay"].asBool()) request["x_queue_okay"].asBool())
{ {
fee = std::max(loadFee, limit); fee = std::max(loadFee, limit);
} }

View File

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

View File

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