Files
rippled/include/xrpl/ledger/ApplyView.h
2026-07-13 10:40:40 +00:00

490 lines
15 KiB
C++

#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h> // IWYU pragma: keep
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STVector256.h>
#include <cstdint>
#include <functional>
#include <optional>
#include <type_traits>
namespace xrpl {
// Bitwise flag enum with existing operator overloads
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum ApplyFlags : std::uint32_t {
TapNone = 0x00,
// This is a local transaction with the
// fail_hard flag set.
TapFailHard = 0x10,
// This is not the transaction's last pass
// Transaction can be retried, soft failures allowed
TapRetry = 0x20,
// Transaction came from a privileged source
TapUnlimited = 0x400,
// Transaction is executing as part of a batch
TapBatch = 0x800,
// Transaction shouldn't be applied
// Signatures shouldn't be checked
TapDryRun = 0x1000
};
constexpr ApplyFlags
operator|(ApplyFlags const& lhs, ApplyFlags const& rhs)
{
return safeCast<ApplyFlags>(
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) |
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
}
static_assert((TapFailHard | TapRetry) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
static_assert((TapRetry | TapFailHard) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
constexpr ApplyFlags
operator&(ApplyFlags const& lhs, ApplyFlags const& rhs)
{
return safeCast<ApplyFlags>(
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) &
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
}
static_assert((TapFailHard & TapRetry) == TapNone, "ApplyFlags operator &");
static_assert((TapRetry & TapFailHard) == TapNone, "ApplyFlags operator &");
constexpr ApplyFlags
operator~(ApplyFlags const& flags)
{
return safeCast<ApplyFlags>(~safeCast<std::underlying_type_t<ApplyFlags>>(flags));
}
static_assert(~TapRetry == safeCast<ApplyFlags>(0xFFFFFFDFu), "ApplyFlags operator ~");
inline ApplyFlags
operator|=(ApplyFlags& lhs, ApplyFlags const& rhs)
{
lhs = lhs | rhs;
return lhs;
}
inline ApplyFlags
operator&=(ApplyFlags& lhs, ApplyFlags const& rhs)
{
lhs = lhs & rhs;
return lhs;
}
//------------------------------------------------------------------------------
/**
* Writeable view to a ledger, for applying a transaction.
*
* This refinement of ReadView provides an interface where
* the SLE can be "checked out" for modifications and put
* back in an updated or removed state. Also added is an
* interface to provide contextual information necessary
* to calculate the results of transaction processing,
* including the metadata if the view is later applied to
* the parent (using an interface in the derived class).
* The context info also includes values from the base
* ledger such as sequence number and the network time.
*
* This allows implementations to journal changes made to
* the state items in a ledger, with the option to apply
* those changes to the base or discard the changes without
* affecting the base.
*
* Typical usage is to call read() for non-mutating
* operations.
*
* For mutating operations the sequence is as follows:
*
* // Add a new value
* v.insert(sle);
*
* // Check out a value for modification
* sle = v.peek(k);
*
* // Indicate that changes were made
* v.update(sle)
*
* // Or, erase the value
* v.erase(sle)
*
* The invariant is that insert, update, and erase may not
* be called with any SLE which belongs to different view.
*/
class ApplyView : public ReadView
{
private:
/**
* Add an entry to a directory using the specified insert strategy
*/
std::optional<std::uint64_t>
dirAdd(
bool preserveOrder,
Keylet const& directory,
uint256 const& key,
std::function<void(SLE::ref)> const& describe);
public:
ApplyView() = default;
/**
* Returns the tx apply flags.
*
* Flags can affect the outcome of transaction
* processing. For example, transactions applied
* to an open ledger generate "local" failures,
* while transactions applied to the consensus
* ledger produce hard failures (and claim a fee).
*/
[[nodiscard]] virtual ApplyFlags
flags() const = 0;
/**
* Prepare to modify the SLE associated with key.
*
* Effects:
*
* Gives the caller ownership of a modifiable
* SLE associated with the specified key.
*
* The returned SLE may be used in a subsequent
* call to erase or update.
*
* The SLE must not be passed to any other ApplyView.
*
* @return `nullptr` if the key is not present
*/
virtual SLE::pointer
peek(Keylet const& k) = 0;
/**
* Remove a peeked SLE.
*
* Requirements:
*
* `sle` was obtained from prior call to peek()
* on this instance of the RawView.
*
* Effects:
*
* The key is no longer associated with the SLE.
*/
virtual void
erase(SLE::ref sle) = 0;
/**
* Insert a new state SLE
*
* Requirements:
*
* `sle` was not obtained from any calls to
* peek() on any instances of RawView.
*
* The SLE's key must not already exist.
*
* Effects:
*
* The key in the state map is associated
* with the SLE.
*
* The RawView acquires ownership of the shared_ptr.
*
* @note The key is taken from the SLE
*/
virtual void
insert(SLE::ref sle) = 0;
/**
* Indicate changes to a peeked SLE
*
* Requirements:
*
* The SLE's key must exist.
*
* `sle` was obtained from prior call to peek()
* on this instance of the RawView.
*
* Effects:
*
* The SLE is updated
*
* @note The key is taken from the SLE
*/
/** @{ */
virtual void
update(SLE::ref sle) = 0;
//--------------------------------------------------------------------------
// Called when a credit is made to an account
// This is required to support PaymentSandbox
virtual void
creditHookIOU(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
STAmount const& preCreditBalance)
{
XRPL_ASSERT(amount.holds<Issue>(), "creditHookIOU: amount is for Issue");
}
virtual void
creditHookMPT(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer)
{
XRPL_ASSERT(amount.holds<MPTIssue>(), "creditHookMPT: amount is for MPTIssue");
}
/**
* Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
* Unlike IOU, MPT doesn't have bi-directional relationship with an issuer,
* where a trustline limits an amount that can be issued to a holder.
* Consequently, the credit step (last MPTEndpointStep or
* BookStep buying MPT) might temporarily overflow OutstandingAmount.
* Limiting of a step's output amount in this case is delegated to
* the next step (in rev order). The next step always redeems when a holder
* account sells MPT (first MPTEndpointStep or BookStep selling MPT).
* In this case the holder account is only limited by the step's output
* and it's available funds since it's transferring the funds from one
* account to another account and doesn't change OutstandingAmount.
* This doesn't apply to an offer owned by an issuer.
* In this case the issuer sells or self debits and is increasing
* OutstandingAmount. Ability to issue is limited by the issuer
* originally available funds less already self sold MPT amounts (MPT sell
* offer).
* Consider an example:
* - GW creates MPT(USD) with 1,000USD MaximumAmount.
* - GW pays 950USD to A1.
* - A1 creates an offer 100XRP(buy)/100USD(sell).
* - GW creates an offer 100XRP(buy)/100USD(sell).
* - A2 pays 200USD to A3 with sendMax of 200XRP.
* Since the payment engine executes payments in reverse,
* OutstandingAmount overflows in MPTEndpointStep: 950 + 200 = 1,150USD.
* BookStep first consumes A1 offer. This reduces OutstandingAmount
* by 100USD: 1,150 - 100 = 1,050USD. GW offer can only be partially
* consumed because the initial available amount is 50USD = 1,000 - 950.
* BookStep limits it's output to 150USD. This in turn limits A3's send
* amount to 150XRP: A1 buys 100XRP and sells 100USD to A3. This doesn't
* change OutstandingAmount. GW buys 50XRP and sells 50USD to A3. This
* changes OutstandingAmount to 1,000USD.
*/
virtual void
issuerSelfDebitHookMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance)
{
}
// Called when the owner count changes
// This is required to support PaymentSandbox
virtual void
adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next)
{
}
/**
* Append an entry to a directory
*
* Entries in the directory will be stored in order of insertion, i.e. new
* entries will always be added at the tail end of the last page.
*
* @param directory the base of the directory
* @param key the entry to insert
* @param describe callback to add required entries to a new page
*
* @return a @c std::optional which, if insertion was successful,
* will contain the page number in which the item was stored.
*
* @note this function may create a page (including a root page), if no
* page with space is available. This function will only fail if the
* page counter exceeds the protocol-defined maximum number of
* allowable pages.
*/
/** @{ */
std::optional<std::uint64_t>
dirAppend(
Keylet const& directory,
Keylet const& key,
std::function<void(SLE::ref)> const& describe)
{
if (key.type != ltOFFER)
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::ApplyView::dirAppend : only Offers are appended to "
"book directories");
// Only Offers are appended to book directories. Call dirInsert()
// instead
return std::nullopt;
// LCOV_EXCL_STOP
}
return dirAdd(true, directory, key.key, describe);
}
/** @} */
/**
* Insert an entry to a directory
*
* Entries in the directory will be stored in a semi-random order, but
* each page will be maintained in sorted order.
*
* @param directory the base of the directory
* @param key the entry to insert
* @param describe callback to add required entries to a new page
*
* @return a @c std::optional which, if insertion was successful,
* will contain the page number in which the item was stored.
*
* @note this function may create a page (including a root page), if no
* page with space is available.this function will only fail if the
* page counter exceeds the protocol-defined maximum number of
* allowable pages.
*/
/** @{ */
std::optional<std::uint64_t>
dirInsert(
Keylet const& directory,
uint256 const& key,
std::function<void(SLE::ref)> const& describe)
{
return dirAdd(false, directory, key, describe);
}
std::optional<std::uint64_t>
dirInsert(
Keylet const& directory,
Keylet const& key,
std::function<void(SLE::ref)> const& describe)
{
return dirAdd(false, directory, key.key, describe);
}
/** @} */
/**
* Remove an entry from a directory
*
* @param directory the base of the directory
* @param page the page number for this page
* @param key the entry to remove
* @param keepRoot if deleting the last entry, don't
* delete the root page (i.e. the directory itself).
*
* @return @c true if the entry was found and deleted and
* @c false otherwise.
*
* @note This function will remove zero or more pages from the directory;
* the root page will not be deleted even if it is empty, unless
* \p keepRoot is not set and the directory is empty.
*/
/** @{ */
bool
dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot);
bool
dirRemove(Keylet const& directory, std::uint64_t page, Keylet const& key, bool keepRoot)
{
return dirRemove(directory, page, key.key, keepRoot);
}
/** @} */
/**
* Remove the specified directory, invoking the callback for every node.
*/
bool
dirDelete(Keylet const& directory, std::function<void(uint256 const&)> const&);
/**
* Remove the specified directory, if it is empty.
*
* @param directory the identifier of the directory node to be deleted
* @return @c true if the directory was found and was successfully deleted
* @c false otherwise.
*
* @note The function should only be called with the root entry (i.e. with
* the first page) of a directory.
*/
bool
emptyDirDelete(Keylet const& directory);
};
/**
* Bundles the mutable ledger view and the transaction being applied.
*
* Passed together to avoid threading two separate parameters through every
* helper that needs both the view (for state reads/writes) and the
* transaction (for field inspection and metadata).
*
* Both members are non-owning references; the caller is responsible for
* ensuring that the referenced objects outlive the ApplyViewContext.
*
* TODO: replace with ApplyContext after it's untangled with xrpl/tx
*/
struct ApplyViewContext
{
ApplyView& view;
STTx const& tx;
};
namespace directory {
/**
* Helper functions for managing low-level directory operations.
* These are not part of the ApplyView interface.
*
* Don't use them unless you really, really know what you're doing.
* Instead use dirAdd, dirInsert, etc.
*/
std::uint64_t
createRoot(
ApplyView& view,
Keylet const& directory,
uint256 const& key,
std::function<void(SLE::ref)> const& describe);
auto
findPreviousPage(ApplyView& view, Keylet const& directory, SLE::ref start);
std::uint64_t
insertKey(
ApplyView& view,
SLE::ref node,
std::uint64_t page,
bool preserveOrder,
STVector256& indexes,
uint256 const& key);
std::optional<std::uint64_t>
insertPage(
ApplyView& view,
std::uint64_t page,
SLE::pointer node,
std::uint64_t nextPage,
SLE::ref next,
uint256 const& key,
Keylet const& directory,
std::function<void(SLE::ref)> const& describe);
} // namespace directory
} // namespace xrpl