Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve deterministic transaction sorting in TxQ #4077

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions src/ripple/app/misc/TxQ.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <ripple/app/tx/applySteps.h>
#include <ripple/ledger/ApplyView.h>
#include <ripple/ledger/OpenView.h>
#include <ripple/protocol/RippleLedgerHash.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/SeqProxy.h>
#include <ripple/protocol/TER.h>
Expand Down Expand Up @@ -340,7 +341,7 @@ class TxQ
in the queue.
*/
std::vector<TxDetails>
getAccountTxs(AccountID const& account, ReadView const& view) const;
getAccountTxs(AccountID const& account) const;

/** Returns information about all transactions currently
in the queue.
Expand All @@ -349,7 +350,7 @@ class TxQ
in the queue.
*/
std::vector<TxDetails>
getTxs(ReadView const& view) const;
getTxs() const;

/** Summarize current fee metrics for the `fee` RPC command.

Expand Down Expand Up @@ -575,6 +576,16 @@ class TxQ
*/
static constexpr int retriesAllowed = 10;

/** The hash of the parent ledger.

This is used to pseudo-randomize the transaction order when
populating byFee_, by XORing it with the transaction hash (txID).
Using a single static and doing the XOR operation every time was
tested to be as fast or faster than storing the computed "sort key",
and obviously uses less memory.
*/
static LedgerHash parentHashComp;

public:
/// Constructor
MaybeTx(
Expand Down Expand Up @@ -621,22 +632,26 @@ class TxQ
explicit OrderCandidates() = default;

/** Sort @ref MaybeTx by `feeLevel` descending, then by
* transaction ID ascending
* pseudo-randomized transaction ID ascending
*
* The transaction queue is ordered such that transactions
* paying a higher fee are in front of transactions paying
* a lower fee, giving them an opportunity to be processed into
* the open ledger first. Within transactions paying the same
* fee, order by the arbitrary but consistent transaction ID.
* This allows validators to build similar queues in the same
* order, and thus have more similar initial proposals.
* fee, order by the arbitrary but consistent pseudo-randomized
* transaction ID. The ID is pseudo-randomized by XORing it with
* the open ledger's parent hash, which is deterministic, but
* unpredictable. This allows validators to build similar queues
* in the same order, and thus have more similar initial
* proposals.
*
*/
bool
operator()(const MaybeTx& lhs, const MaybeTx& rhs) const
{
if (lhs.feeLevel == rhs.feeLevel)
return lhs.txID < rhs.txID;
return (lhs.txID ^ MaybeTx::parentHashComp) <
(rhs.txID ^ MaybeTx::parentHashComp);
Comment on lines +653 to +654
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doxygen comment preceding this change probably needs to be updated.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

return lhs.feeLevel > rhs.feeLevel;
}
};
Expand Down Expand Up @@ -770,6 +785,14 @@ class TxQ
*/
std::optional<size_t> maxSize_;

#if !NDEBUG
/**
parentHash_ checks that no unexpected ledger transitions
happen, and is only checked via debug asserts.
*/
LedgerHash parentHash_{beast::zero};
#endif

/** Most queue operations are done under the master lock,
but use this mutex for the RPC "fee" command, which isn't.
*/
Expand Down
40 changes: 36 additions & 4 deletions src/ripple/app/misc/impl/TxQ.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ TxQ::FeeMetrics::escalatedSeriesFeeLevel(
return totalFeeLevel;
}

LedgerHash TxQ::MaybeTx::parentHashComp{};

TxQ::MaybeTx::MaybeTx(
std::shared_ptr<STTx const> const& txn_,
TxID const& txID_,
Expand Down Expand Up @@ -467,13 +469,12 @@ TxQ::eraseAndAdvance(TxQ::FeeMultiSet::const_iterator_type candidateIter)

// Check if the next transaction for this account is earlier in the queue,
// which means we skipped it earlier, and need to try it again.
OrderCandidates o;
auto const feeNextIter = std::next(candidateIter);
bool const useAccountNext =
accountNextIter != txQAccount.transactions.end() &&
accountNextIter->first > candidateIter->seqProxy &&
(feeNextIter == byFee_.end() ||
o(accountNextIter->second, *feeNextIter));
byFee_.value_comp()(accountNextIter->second, *feeNextIter));

auto const candidateNextIter = byFee_.erase(candidateIter);
txQAccount.transactions.erase(accountIter);
Expand Down Expand Up @@ -1529,6 +1530,37 @@ TxQ::accept(Application& app, OpenView& view)
}
}

// All transactions that can be moved out of the queue into the open
// ledger have been. Rebuild the queue using the open ledger's
// parent hash, so that transactions paying the same fee are
// reordered.
LedgerHash const& parentHash = view.info().parentHash;
#if !NDEBUG
auto const startingSize = byFee_.size();
assert(parentHash != parentHash_);
parentHash_ = parentHash;
#endif
// byFee_ doesn't "own" the candidate objects inside it, so it's
// perfectly safe to wipe it and start over, repopulating from
// byAccount_.
//
// In the absence of a "re-sort the list in place" function, this
// was the fastest method tried to repopulate the list.
// Other methods included: create a new list and moving items over one at a
// time, create a new list and merge the old list into it.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate you trying the various techniques and leaving a comment that you did so. That leaves me less inclined to do my own experiments.

byFee_.clear();

MaybeTx::parentHashComp = parentHash;

for (auto& [_, account] : byAccount_)
{
for (auto& [_, candidate] : account.transactions)
{
byFee_.insert(candidate);
}
}
assert(byFee_.size() == startingSize);
thejohnfreeman marked this conversation as resolved.
Show resolved Hide resolved

return ledgerChanged;
}

Expand Down Expand Up @@ -1740,7 +1772,7 @@ TxQ::getTxRequiredFeeAndSeq(
}

std::vector<TxQ::TxDetails>
TxQ::getAccountTxs(AccountID const& account, ReadView const& view) const
TxQ::getAccountTxs(AccountID const& account) const
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Thanks for removing the unused argument.

{
std::vector<TxDetails> result;

Expand All @@ -1761,7 +1793,7 @@ TxQ::getAccountTxs(AccountID const& account, ReadView const& view) const
}

std::vector<TxQ::TxDetails>
TxQ::getTxs(ReadView const& view) const
TxQ::getTxs() const
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spotting! Thanks for removing the unused argument.

{
std::vector<TxDetails> result;

Expand Down
5 changes: 2 additions & 3 deletions src/ripple/rpc/handlers/AccountInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ doAccountInfo(RPC::JsonContext& context)
{
Json::Value jvQueueData = Json::objectValue;

auto const txs =
context.app.getTxQ().getAccountTxs(accountID, *ledger);
auto const txs = context.app.getTxQ().getAccountTxs(accountID);
if (!txs.empty())
{
jvQueueData[jss::txn_count] =
Expand Down Expand Up @@ -298,7 +297,7 @@ doAccountInfoGrpc(
return {result, errorStatus};
}
std::vector<TxQ::TxDetails> const txs =
context.app.getTxQ().getAccountTxs(accountID, *ledger);
context.app.getTxQ().getAccountTxs(accountID);
org::xrpl::rpc::v1::QueueData& queueData =
*result.mutable_queue_data();
RPC::convert(queueData, txs);
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/rpc/handlers/LedgerHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ LedgerHandler::check()
return rpcINVALID_PARAMS;
}

queueTxs_ = context_.app.getTxQ().getTxs(*ledger_);
queueTxs_ = context_.app.getTxQ().getTxs();
}

return Status::OK;
Expand Down
Loading