switch to list of valid entry types

This commit is contained in:
Mayukha Vadari
2026-06-10 19:02:35 -04:00
parent 761ff63daa
commit 39158a914a
13 changed files with 229 additions and 130 deletions

View File

@@ -5,6 +5,7 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <optional>
@@ -20,6 +21,9 @@ class ValidAMM
public:
enum class ZeroAllowed : bool { No = false, Yes = true };
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltAMM, ltRIPPLE_STATE, ltACCOUNT_ROOT, ltMPTOKEN>{};
ValidAMM() = default;
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -6,6 +6,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <memory>
@@ -17,6 +18,8 @@ class ValidBookDirectory
hash_set<uint256> rootIndexes_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltDIR_NODE>{};
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);

View File

@@ -6,6 +6,7 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <map>
#include <vector>
@@ -38,6 +39,9 @@ class TransfersNotFrozen
std::map<AccountID, SLE::const_pointer const> possibleIssuers_;
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltACCOUNT_ROOT, ltRIPPLE_STATE>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -8,6 +8,7 @@
#include <xrpl/tx/invariants/AMMInvariant.h>
#include <xrpl/tx/invariants/DirectoryInvariant.h>
#include <xrpl/tx/invariants/FreezeInvariant.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
#include <xrpl/tx/invariants/LoanInvariant.h>
#include <xrpl/tx/invariants/MPTInvariant.h>
@@ -107,6 +108,8 @@ public:
class TransactionFeeCheck
{
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitNoLedgerEntryTypes{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -127,6 +130,9 @@ class XRPNotCreated
std::int64_t drops_ = 0;
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltACCOUNT_ROOT, ltPAYCHAN, ltESCROW>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -147,6 +153,8 @@ class AccountRootsNotDeleted
std::uint32_t accountsDeleted_ = 0;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltACCOUNT_ROOT>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -174,6 +182,8 @@ class AccountRootsDeletedClean
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> accountsDeleted_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltACCOUNT_ROOT>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -193,6 +203,8 @@ class XRPBalanceChecks
bool bad_ = false;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltACCOUNT_ROOT>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -210,6 +222,8 @@ class LedgerEntryTypesMatch
bool invalidTypeAdded_ = false;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitAllLedgerEntryTypes{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -228,6 +242,8 @@ class NoXRPTrustLines
bool xrpTrustLine_ = false;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltRIPPLE_STATE>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -247,6 +263,8 @@ class NoDeepFreezeTrustLinesWithoutFreeze
bool deepFreezeWithoutFreeze_ = false;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltRIPPLE_STATE>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -266,6 +284,8 @@ class NoBadOffers
bool bad_ = false;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltOFFER>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -282,6 +302,9 @@ class NoZeroEscrow
bool bad_ = false;
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltESCROW, ltMPTOKEN_ISSUANCE, ltMPTOKEN>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -302,6 +325,8 @@ class ValidNewAccountRoot
std::uint32_t flags_ = 0;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltACCOUNT_ROOT>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -323,6 +348,9 @@ class ValidClawback
std::uint32_t mptokensChanged_ = 0;
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltRIPPLE_STATE, ltMPTOKEN>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -343,6 +371,8 @@ class ValidPseudoAccounts
std::vector<std::string> errors_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltACCOUNT_ROOT>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -363,6 +393,8 @@ class NoModifiedUnmodifiableFields
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitAllLedgerEntryTypes{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -378,6 +410,8 @@ class ValidAmounts
std::vector<std::shared_ptr<SLE const>> afterEntries_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitAllLedgerEntryTypes{};
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);

View File

@@ -0,0 +1,50 @@
#pragma once
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
struct VisitAllLedgerEntryTypes
{
static constexpr bool visitsAll = true;
static constexpr bool visitsNone = false;
static constexpr bool empty = false;
template <LedgerEntryType>
static consteval bool
contains()
{
return true;
}
};
struct VisitNoLedgerEntryTypes
{
static constexpr bool visitsAll = false;
static constexpr bool visitsNone = true;
static constexpr bool empty = true;
template <LedgerEntryType>
static consteval bool
contains()
{
return false;
}
};
template <LedgerEntryType... Types>
struct VisitLedgerEntryTypes
{
static constexpr bool visitsAll = false;
static constexpr bool visitsNone = false;
static constexpr bool empty = sizeof...(Types) == 0;
template <LedgerEntryType Type>
static consteval bool
contains()
{
return ((Type == Types) || ...);
}
};
} // namespace xrpl

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <map>
#include <vector>
@@ -45,6 +46,9 @@ class ValidLoanBroker
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j);
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltLOAN_BROKER, ltACCOUNT_ROOT, ltRIPPLE_STATE, ltMPTOKEN>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -4,6 +4,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <vector>
@@ -22,6 +23,8 @@ class ValidLoan
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltLOAN>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -5,6 +5,7 @@
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <cstdint>
#include <memory>
@@ -36,6 +37,9 @@ class ValidMPTIssuance
std::vector<std::shared_ptr<SLE const>> deletedHoldings_;
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltMPTOKEN_ISSUANCE, ltMPTOKEN, ltRIPPLE_STATE>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -64,6 +68,9 @@ class ValidMPTPayment
hash_map<uint192, MPTData> data_;
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltMPTOKEN_ISSUANCE, ltMPTOKEN>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -85,6 +92,8 @@ class ValidMPTTransfer
hash_map<uint256, bool> deletedAuthorized_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltMPTOKEN>{};
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <cstdint>
@@ -32,6 +33,8 @@ class ValidNFTokenPage
bool deletedLink_ = false;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltNFTOKEN_PAGE>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
@@ -60,6 +63,8 @@ class NFTokenCountTracking
std::uint32_t afterBurnedTotal_ = 0;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltACCOUNT_ROOT>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
namespace xrpl {
@@ -17,6 +18,8 @@ class ValidPermissionedDEX
hash_set<uint256> domains_;
public:
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<ltDIR_NODE, ltOFFER>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -4,6 +4,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <vector>
@@ -31,6 +32,9 @@ class ValidPermissionedDomain
std::vector<SleStatus> sleStatus_;
public:
static constexpr auto kRelevantLedgerEntryTypes =
VisitLedgerEntryTypes<ltPERMISSIONED_DOMAIN>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -9,6 +9,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntryTypes.h>
#include <optional>
#include <unordered_map>
@@ -153,6 +154,13 @@ public:
[[nodiscard]] static std::int32_t
computeCoarsestScale(std::vector<DeltaInfo> const& numbers);
static constexpr auto kRelevantLedgerEntryTypes = VisitLedgerEntryTypes<
ltVAULT,
ltMPTOKEN_ISSUANCE,
ltMPTOKEN,
ltACCOUNT_ROOT,
ltRIPPLE_STATE>{};
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);

View File

@@ -25,6 +25,7 @@
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
#include <xrpl/tx/invariants/VaultInvariant.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <exception>
@@ -38,79 +39,68 @@ namespace xrpl {
namespace {
template <class... Checkers>
struct InvariantCheckTypes
{
};
template <class Checker>
using RelevantLedgerEntryTypes = std::remove_cvref_t<decltype(Checker::kRelevantLedgerEntryTypes)>;
using RoutedInvariantCheckTypes = InvariantCheckTypes<
TransactionFeeCheck,
AccountRootsNotDeleted,
AccountRootsDeletedClean,
LedgerEntryTypesMatch,
XRPBalanceChecks,
XRPNotCreated,
NoXRPTrustLines,
NoDeepFreezeTrustLinesWithoutFreeze,
TransfersNotFrozen,
NoBadOffers,
NoZeroEscrow,
ValidNewAccountRoot,
ValidNFTokenPage,
NFTokenCountTracking,
ValidClawback,
ValidMPTIssuance,
ValidPermissionedDomain,
ValidPermissionedDEX,
ValidBookDirectory,
ValidAMM,
NoModifiedUnmodifiableFields,
ValidPseudoAccounts,
ValidLoanBroker,
ValidLoan,
ValidVault,
ValidMPTPayment,
ValidAmounts,
ValidMPTTransfer>;
template <class Checker>
concept HasRelevantLedgerEntryTypes = requires { Checker::kRelevantLedgerEntryTypes; };
template <class Checker, class... Checkers>
consteval std::size_t
countInvariantCheck(InvariantCheckTypes<Checkers...>)
template <class Checker>
consteval bool
hasInvariantVisitRoute()
{
return (std::size_t{0} + ... + (std::is_same_v<Checker, Checkers> ? 1 : 0));
if constexpr (HasRelevantLedgerEntryTypes<Checker>)
{
using Types = RelevantLedgerEntryTypes<Checker>;
return Types::visitsAll || Types::visitsNone || !Types::empty;
}
return false;
}
template <std::size_t... Is>
consteval bool
allInvariantChecksAreRouted(std::index_sequence<Is...>)
allInvariantChecksHaveVisitRoutes(std::index_sequence<Is...>)
{
return (
(countInvariantCheck<std::tuple_element_t<Is, InvariantChecks>>(
RoutedInvariantCheckTypes{}) == 1) &&
...);
return (hasInvariantVisitRoute<std::tuple_element_t<Is, InvariantChecks>>() && ...);
}
static_assert(
allInvariantChecksAreRouted(std::make_index_sequence<std::tuple_size_v<InvariantChecks>>{}),
"Every invariant check must be routed exactly once.");
allInvariantChecksHaveVisitRoutes(
std::make_index_sequence<std::tuple_size_v<InvariantChecks>>{}),
"Every invariant check must declare ledger-entry visit routing.");
template <class... Checkers>
template <class Checker>
void
visitInvariantChecks(
visitAllEntryInvariantCheck(
InvariantChecks& checkers,
bool isDelete,
SLE::const_ref before,
SLE::const_ref after)
{
(..., std::get<Checkers>(checkers).visitEntry(isDelete, before, after));
if constexpr (RelevantLedgerEntryTypes<Checker>::visitsAll)
std::get<Checker>(checkers).visitEntry(isDelete, before, after);
}
template <std::size_t... Is>
void
visitAllEntryInvariantChecks(
InvariantChecks& checkers,
bool isDelete,
SLE::const_ref before,
SLE::const_ref after,
std::index_sequence<Is...>)
{
(...,
visitAllEntryInvariantCheck<std::tuple_element_t<Is, InvariantChecks>>(
checkers, isDelete, before, after));
}
[[nodiscard]] std::optional<LedgerEntryType>
entryTypeForSpecificInvariants(SLE::const_ref before, SLE::const_ref after)
entryTypeForMappedInvariants(SLE::const_ref before, SLE::const_ref after)
{
if (before && after && before->getType() != after->getType())
{
// LedgerEntryTypesMatch runs as a broad invariant and reports this.
// LedgerEntryTypesMatch runs as an all-entry invariant and reports this.
return std::nullopt;
}
@@ -121,102 +111,68 @@ entryTypeForSpecificInvariants(SLE::const_ref before, SLE::const_ref after)
return std::nullopt;
}
template <LedgerEntryType Type, class Checker>
void
visitTypeSpecificInvariantChecks(
visitLedgerTypeInvariantCheck(
InvariantChecks& checkers,
LedgerEntryType type,
bool isDelete,
SLE::const_ref before,
SLE::const_ref after)
{
switch (type)
using Types = RelevantLedgerEntryTypes<Checker>;
if constexpr (!Types::visitsAll && !Types::visitsNone && Types::template contains<Type>())
{
case ltACCOUNT_ROOT:
visitInvariantChecks<
AccountRootsNotDeleted,
AccountRootsDeletedClean,
XRPBalanceChecks,
TransfersNotFrozen,
ValidNewAccountRoot,
NFTokenCountTracking,
ValidAMM,
ValidPseudoAccounts,
ValidLoanBroker,
ValidVault>(checkers, isDelete, before, after);
break;
case ltRIPPLE_STATE:
visitInvariantChecks<
NoXRPTrustLines,
NoDeepFreezeTrustLinesWithoutFreeze,
TransfersNotFrozen,
ValidMPTIssuance,
ValidAMM,
ValidLoanBroker,
ValidVault>(checkers, isDelete, before, after);
break;
case ltOFFER:
visitInvariantChecks<NoBadOffers, ValidPermissionedDEX>(
checkers, isDelete, before, after);
break;
case ltESCROW:
visitInvariantChecks<NoZeroEscrow>(checkers, isDelete, before, after);
break;
case ltNFTOKEN_PAGE:
visitInvariantChecks<ValidNFTokenPage>(checkers, isDelete, before, after);
break;
case ltMPTOKEN_ISSUANCE:
visitInvariantChecks<NoZeroEscrow, ValidMPTIssuance, ValidVault, ValidMPTPayment>(
checkers, isDelete, before, after);
break;
case ltMPTOKEN:
visitInvariantChecks<
NoZeroEscrow,
ValidClawback,
ValidMPTIssuance,
ValidAMM,
ValidLoanBroker,
ValidVault,
ValidMPTPayment,
ValidMPTTransfer>(checkers, isDelete, before, after);
break;
case ltPERMISSIONED_DOMAIN:
visitInvariantChecks<ValidPermissionedDomain>(checkers, isDelete, before, after);
break;
case ltDIR_NODE:
visitInvariantChecks<ValidPermissionedDEX, ValidBookDirectory>(
checkers, isDelete, before, after);
break;
case ltAMM:
visitInvariantChecks<ValidAMM>(checkers, isDelete, before, after);
break;
case ltLOAN_BROKER:
visitInvariantChecks<ValidLoanBroker>(checkers, isDelete, before, after);
break;
case ltLOAN:
visitInvariantChecks<ValidLoan>(checkers, isDelete, before, after);
break;
case ltVAULT:
visitInvariantChecks<ValidVault>(checkers, isDelete, before, after);
break;
default:
break;
std::get<Checker>(checkers).visitEntry(isDelete, before, after);
}
}
template <LedgerEntryType Type, std::size_t... Is>
void
visitBroadInvariantChecks(
visitLedgerTypeInvariantChecks(
InvariantChecks& checkers,
bool isDelete,
SLE::const_ref before,
SLE::const_ref after,
std::index_sequence<Is...>)
{
(...,
visitLedgerTypeInvariantCheck<Type, std::tuple_element_t<Is, InvariantChecks>>(
checkers, isDelete, before, after));
}
using LedgerTypeInvariantVisitor = void (*)(InvariantChecks&, bool, SLE::const_ref, SLE::const_ref);
template <LedgerEntryType Type>
void
visitMappedLedgerTypeInvariantChecks(
InvariantChecks& checkers,
bool isDelete,
SLE::const_ref before,
SLE::const_ref after)
{
visitInvariantChecks<
LedgerEntryTypesMatch,
XRPNotCreated,
NoModifiedUnmodifiableFields,
ValidAmounts>(checkers, isDelete, before, after);
visitLedgerTypeInvariantChecks<Type>(
checkers,
isDelete,
before,
after,
std::make_index_sequence<std::tuple_size_v<InvariantChecks>>{});
}
#pragma push_macro("LEDGER_ENTRY")
#undef LEDGER_ENTRY
#define LEDGER_ENTRY(tag, ...) \
std::pair<LedgerEntryType, LedgerTypeInvariantVisitor>{ \
tag, &visitMappedLedgerTypeInvariantChecks<tag>},
static constexpr auto kLedgerTypeInvariantVisitors =
std::to_array<std::pair<LedgerEntryType, LedgerTypeInvariantVisitor>>({
#include <xrpl/protocol/detail/ledger_entries.macro>
});
#undef LEDGER_ENTRY
#pragma pop_macro("LEDGER_ENTRY")
} // namespace
ApplyContext::ApplyContext(
@@ -296,10 +252,22 @@ ApplyContext::checkInvariantsHelper(
// call each check's per-entry method
visit([&checkers](
uint256 const&, bool isDelete, SLE::const_ref before, SLE::const_ref after) {
visitBroadInvariantChecks(checkers, isDelete, before, after);
visitAllEntryInvariantChecks(
checkers,
isDelete,
before,
after,
std::make_index_sequence<std::tuple_size_v<InvariantChecks>>{});
if (auto const type = entryTypeForSpecificInvariants(before, after))
visitTypeSpecificInvariantChecks(checkers, *type, isDelete, before, after);
if (auto const type = entryTypeForMappedInvariants(before, after))
{
auto const iter = std::find_if(
kLedgerTypeInvariantVisitors.cbegin(),
kLedgerTypeInvariantVisitors.cend(),
[type](auto const& visitor) { return visitor.first == *type; });
if (iter != kLedgerTypeInvariantVisitors.cend())
iter->second(checkers, isDelete, before, after);
}
});
// Note: do not replace this logic with a `...&&` fold expression.