mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-05 16:57:53 +00:00
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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
Reference in New Issue
Block a user