mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-23 15:10:34 +00:00
490 lines
15 KiB
C++
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
|