mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-27 23:38:08 +00:00
phase-4 renamed the shared close_time attribute to close_time_ripple_epoch_s, naming its unit and epoch. Two consumers here still referenced the old spelling and broke the build once the rename merged forward: the re-export in LedgerSpanNames.h and the setAttribute call in BuildLedger.cpp. Both are phase-6 content, so they are fixed here rather than upstream.
281 lines
9.7 KiB
C++
281 lines
9.7 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::closeTimeRippleEpochS,
|
|
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");
|
|
// Repeated from the parent ledger.build span on purpose: TraceQL cannot
|
|
// reach a parent's attributes from a child, so without it no query can
|
|
// select this span by ledger. `view` is the accumulator over the ledger
|
|
// being built and copies its header, so this is the same sequence number
|
|
// the parent reports.
|
|
applySpan.setAttribute(ledger_span::attr::ledgerSeq, static_cast<int64_t>(view.seq()));
|
|
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
|