mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 22:30:57 +00:00
ledger.build was left as unscoped SpanGuard after the type split, so tx.apply (created synchronously during applyTxs on the same JtAccept worker) no longer nested under it. buildLedgerImpl runs synchronously with no yield, so ScopedSpanGuard is safe and restores the ledger.build -> tx.apply edge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
274 lines
9.2 KiB
C++
274 lines
9.2 KiB
C++
#include <xrpld/app/ledger/BuildLedger.h>
|
|
|
|
#include <xrpld/app/ledger/LedgerReplay.h>
|
|
#include <xrpld/app/ledger/OpenLedger.h>
|
|
#include <xrpld/app/ledger/detail/LedgerSpanNames.h>
|
|
#include <xrpld/app/main/Application.h>
|
|
|
|
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/basics/chrono.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/ledger/ApplyView.h>
|
|
#include <xrpl/ledger/CanonicalTXSet.h>
|
|
#include <xrpl/ledger/Ledger.h>
|
|
#include <xrpl/ledger/OpenView.h>
|
|
#include <xrpl/nodestore/NodeObject.h>
|
|
#include <xrpl/protocol/Indexes.h> // IWYU pragma: keep
|
|
#include <xrpl/protocol/LedgerHeader.h>
|
|
#include <xrpl/protocol/Protocol.h>
|
|
#include <xrpl/protocol/SystemParameters.h> // IWYU pragma: keep
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/telemetry/SpanGuard.h>
|
|
#include <xrpl/telemetry/SpanNames.h>
|
|
#include <xrpl/tx/apply.h>
|
|
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <memory>
|
|
#include <set>
|
|
|
|
namespace xrpl {
|
|
|
|
/* Generic buildLedgerImpl that dispatches to ApplyTxs invocable with signature
|
|
void(OpenView&, std::shared_ptr<Ledger> const&)
|
|
It is responsible for adding transactions to the open view to generate the
|
|
new ledger. It is generic since the mechanics differ for consensus
|
|
generated ledgers versus replayed ledgers.
|
|
*/
|
|
template <class ApplyTxs>
|
|
std::shared_ptr<Ledger>
|
|
buildLedgerImpl(
|
|
std::shared_ptr<Ledger const> const& parent,
|
|
NetClock::time_point closeTime,
|
|
bool const closeTimeCorrect,
|
|
NetClock::duration closeResolution,
|
|
Application& app,
|
|
beast::Journal j,
|
|
ApplyTxs&& applyTxs)
|
|
{
|
|
using namespace telemetry;
|
|
// Scoped so tx.apply (created synchronously below during applyTxs on this
|
|
// thread) nests under it. buildLedgerImpl runs synchronously with no yield.
|
|
auto buildSpan = ScopedSpanGuard(TraceCategory::Ledger, seg::ledger, ledger_span::op::build);
|
|
|
|
auto built = std::make_shared<Ledger>(*parent, closeTime);
|
|
|
|
if (built->isFlagLedger())
|
|
{
|
|
built->updateNegativeUNL();
|
|
}
|
|
|
|
// Set up to write SHAMap changes to our database,
|
|
// perform updates, extract changes
|
|
|
|
{
|
|
OpenView accum(&*built);
|
|
XRPL_ASSERT(!accum.open(), "xrpl::buildLedgerImpl : valid ledger state");
|
|
applyTxs(accum, built);
|
|
accum.apply(*built);
|
|
}
|
|
|
|
built->updateSkipList();
|
|
{
|
|
// Write the final version of all modified SHAMap
|
|
// nodes to the node store to preserve the new LCL
|
|
|
|
int const asf = built->stateMap().flushDirty(NodeObjectType::AccountNode);
|
|
int const tmf = built->txMap().flushDirty(NodeObjectType::TransactionNode);
|
|
JLOG(j.debug()) << "Flushed " << asf << " accounts and " << tmf << " transaction nodes";
|
|
}
|
|
built->unshare();
|
|
|
|
// Accept ledger
|
|
XRPL_ASSERT(
|
|
built->header().seq < kXrpLedgerEarliestFees || built->read(keylet::feeSettings()),
|
|
"xrpl::buildLedgerImpl : valid ledger fees");
|
|
built->setAccepted(closeTime, closeResolution, closeTimeCorrect);
|
|
buildSpan.setAttribute(ledger_span::attr::ledgerSeq, static_cast<int64_t>(built->header().seq));
|
|
buildSpan.setAttribute(
|
|
ledger_span::attr::closeTime, static_cast<int64_t>(closeTime.time_since_epoch().count()));
|
|
buildSpan.setAttribute(ledger_span::attr::closeTimeCorrect, closeTimeCorrect);
|
|
buildSpan.setAttribute(
|
|
ledger_span::attr::closeResolutionMs,
|
|
static_cast<int64_t>(
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(closeResolution).count()));
|
|
|
|
return built;
|
|
}
|
|
|
|
/**
|
|
* Apply a set of consensus transactions to a ledger.
|
|
*
|
|
* @param app Handle to application
|
|
* @param txns the set of transactions to apply,
|
|
* @param failed set of transactions that failed to apply
|
|
* @param view ledger to apply to
|
|
* @param j Journal for logging
|
|
* @return number of transactions applied; transactions to retry left in txns
|
|
*/
|
|
|
|
std::size_t
|
|
applyTransactions(
|
|
Application& app,
|
|
std::shared_ptr<Ledger const> const& built,
|
|
CanonicalTXSet& txns,
|
|
std::set<TxID>& failed,
|
|
OpenView& view,
|
|
beast::Journal j)
|
|
{
|
|
using namespace telemetry;
|
|
auto applySpan = SpanGuard::span(TraceCategory::Transactions, seg::tx, ledger_span::op::apply);
|
|
|
|
bool certainRetry = true;
|
|
std::size_t count = 0;
|
|
|
|
// Attempt to apply all of the retriable transactions
|
|
for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass)
|
|
{
|
|
JLOG(j.debug()) << (certainRetry ? "Pass: " : "Final pass: ") << pass << " begins ("
|
|
<< txns.size() << " transactions)";
|
|
int changes = 0;
|
|
|
|
auto it = txns.begin();
|
|
|
|
while (it != txns.end())
|
|
{
|
|
auto const txid = it->first.getTXID();
|
|
|
|
try
|
|
{
|
|
if (pass == 0 && built->txExists(txid))
|
|
{
|
|
it = txns.erase(it);
|
|
continue;
|
|
}
|
|
|
|
switch (applyTransaction(app, view, *it->second, certainRetry, TapNone, j))
|
|
{
|
|
case ApplyTransactionResult::Success:
|
|
it = txns.erase(it);
|
|
++changes;
|
|
break;
|
|
|
|
case ApplyTransactionResult::Fail:
|
|
failed.insert(txid);
|
|
it = txns.erase(it);
|
|
break;
|
|
|
|
case ApplyTransactionResult::Retry:
|
|
++it;
|
|
}
|
|
}
|
|
catch (std::exception const& ex)
|
|
{
|
|
JLOG(j.warn()) << "Transaction " << txid << " throws: " << ex.what();
|
|
failed.insert(txid);
|
|
it = txns.erase(it);
|
|
}
|
|
}
|
|
|
|
JLOG(j.debug()) << (certainRetry ? "Pass: " : "Final pass: ") << pass << " completed ("
|
|
<< changes << " changes)";
|
|
|
|
// Accumulate changes.
|
|
count += changes;
|
|
|
|
// A non-retry pass made no changes
|
|
if ((changes == 0) && !certainRetry)
|
|
break;
|
|
|
|
// Stop retriable passes
|
|
if ((changes == 0) || (pass >= LEDGER_RETRY_PASSES))
|
|
certainRetry = false;
|
|
}
|
|
|
|
// If there are any transactions left, we must have
|
|
// tried them in at least one final pass
|
|
XRPL_ASSERT(txns.empty() || !certainRetry, "xrpl::applyTransactions : retry transactions");
|
|
applySpan.setAttribute(ledger_span::attr::txCount, static_cast<int64_t>(count));
|
|
applySpan.setAttribute(ledger_span::attr::txFailed, static_cast<int64_t>(failed.size()));
|
|
return count;
|
|
}
|
|
|
|
// Build a ledger from consensus transactions
|
|
std::shared_ptr<Ledger>
|
|
buildLedger(
|
|
std::shared_ptr<Ledger const> const& parent,
|
|
NetClock::time_point closeTime,
|
|
bool const closeTimeCorrect,
|
|
NetClock::duration closeResolution,
|
|
Application& app,
|
|
CanonicalTXSet& txns,
|
|
std::set<TxID>& failedTxns,
|
|
beast::Journal j)
|
|
{
|
|
JLOG(j.debug()) << "Report: Transaction Set = " << txns.key() << ", close "
|
|
<< closeTime.time_since_epoch().count()
|
|
<< (closeTimeCorrect ? "" : " (incorrect)");
|
|
|
|
return buildLedgerImpl(
|
|
parent,
|
|
closeTime,
|
|
closeTimeCorrect,
|
|
closeResolution,
|
|
app,
|
|
j,
|
|
[&](OpenView& accum, std::shared_ptr<Ledger> const& built) {
|
|
JLOG(j.debug()) << "Attempting to apply " << txns.size() << " transactions";
|
|
|
|
auto const applied = applyTransactions(app, built, txns, failedTxns, accum, j);
|
|
|
|
if (!txns.empty() || !failedTxns.empty())
|
|
{
|
|
JLOG(j.debug()) << "Applied " << applied << " transactions; " << failedTxns.size()
|
|
<< " failed and " << txns.size() << " will be retried. "
|
|
<< "Total transactions in ledger (including Inner Batch): "
|
|
<< accum.txCount();
|
|
}
|
|
else
|
|
{
|
|
JLOG(j.debug()) << "Applied " << applied << " transactions. "
|
|
<< "Total transactions in ledger (including Inner Batch): "
|
|
<< accum.txCount();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Build a ledger by replaying
|
|
std::shared_ptr<Ledger>
|
|
buildLedger(
|
|
LedgerReplay const& replayData,
|
|
ApplyFlags applyFlags,
|
|
Application& app,
|
|
beast::Journal j)
|
|
{
|
|
auto const& replayLedger = replayData.replay();
|
|
|
|
JLOG(j.debug()) << "Report: Replay Ledger " << replayLedger->header().hash;
|
|
|
|
return buildLedgerImpl(
|
|
replayData.parent(),
|
|
replayLedger->header().closeTime,
|
|
((replayLedger->header().closeFlags & kSLcfNoConsensusTime) == 0),
|
|
replayLedger->header().closeTimeResolution,
|
|
app,
|
|
j,
|
|
[&](OpenView& accum, std::shared_ptr<Ledger> const& built) {
|
|
for (auto& tx : replayData.orderedTxns())
|
|
{
|
|
// Inner batch transactions are applied as part of their outer
|
|
// Batch transaction, never on their own. Skip them here so they
|
|
// are not re-applied a second time outside of the batch during
|
|
// replay.
|
|
if (tx.second->isFlag(tfInnerBatchTxn))
|
|
continue;
|
|
applyTransaction(app, accum, *tx.second, false, applyFlags, j);
|
|
}
|
|
});
|
|
}
|
|
|
|
} // namespace xrpl
|