Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation

This commit is contained in:
Pratik Mankawde
2026-09-22 15:38:40 +01:00
79 changed files with 3798 additions and 125 deletions

View File

@@ -64,6 +64,7 @@ words:
- blindings
- bookdir
- Bougalis
- bthomee
- Britto
- Btrfs
- Buildx

View File

@@ -439,7 +439,7 @@ jobs:
- name: Upload coverage report
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
disable_search: true
disable_telem: true

View File

@@ -57,7 +57,7 @@ jobs:
- name: Upload coverage report
if: ${{ github.repository == 'XRPLF/rippled' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
disable_search: true
disable_telem: true

View File

@@ -518,7 +518,7 @@ public:
* The input must be precisely `2 * bytes` hexadecimal characters
* long, with one exception: the value '0'.
*
* @param sv A null-terminated string of hexadecimal characters
* @param sv A string of hexadecimal characters
* @return true if the input was parsed properly; false otherwise.
*/
[[nodiscard]] constexpr bool

View File

@@ -0,0 +1,45 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class AMMEntry : public SLEBase<ViewT, ltAMM>
{
public:
using Base = SLEBase<ViewT, ltAMM>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit AMMEntry(
Asset const& issue1,
Asset const& issue2,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::amm(issue1, issue2), view, j)
{
}
explicit AMMEntry(
uint256 const& ammID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::amm(ammID), view, j)
{
}
};
using AMMEntryR = AMMEntry<ReadView>;
using AMMEntryW = AMMEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class AccountRootEntry : public SLEBase<ViewT, ltACCOUNT_ROOT>
{
public:
using Base = SLEBase<ViewT, ltACCOUNT_ROOT>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit AccountRootEntry(
AccountID const& id,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::account(id), view, j)
{
}
};
using AccountRootEntryR = AccountRootEntry<ReadView>;
using AccountRootEntryW = AccountRootEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class AmendmentsEntry : public SLEBase<ViewT, ltAMENDMENTS>
{
public:
using Base = SLEBase<ViewT, ltAMENDMENTS>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit AmendmentsEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::amendments(), view, j)
{
}
};
using AmendmentsEntryR = AmendmentsEntry<ReadView>;
using AmendmentsEntryW = AmendmentsEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,36 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STXChainBridge.h>
namespace xrpl {
template <typename ViewT>
class BridgeEntry : public SLEBase<ViewT, ltBRIDGE>
{
public:
using Base = SLEBase<ViewT, ltBRIDGE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit BridgeEntry(
STXChainBridge const& bridge,
STXChainBridge::ChainType chainType,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::bridge(bridge, chainType), view, j)
{
}
};
using BridgeEntryR = BridgeEntry<ReadView>;
using BridgeEntryW = BridgeEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class CheckEntry : public SLEBase<ViewT, ltCHECK>
{
public:
using Base = SLEBase<ViewT, ltCHECK>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit CheckEntry(
AccountID const& id,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::check(id, seq), view, j)
{
}
explicit CheckEntry(
uint256 const& checkID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::check(checkID), view, j)
{
}
};
using CheckEntryR = CheckEntry<ReadView>;
using CheckEntryW = CheckEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,47 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class CredentialEntry : public SLEBase<ViewT, ltCREDENTIAL>
{
public:
using Base = SLEBase<ViewT, ltCREDENTIAL>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit CredentialEntry(
AccountID const& subject,
AccountID const& issuer,
Slice const& credType,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::credential(subject, issuer, credType), view, j)
{
}
explicit CredentialEntry(
uint256 const& credentialID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::credential(credentialID), view, j)
{
}
};
using CredentialEntryR = CredentialEntry<ReadView>;
using CredentialEntryW = CredentialEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class DIDEntry : public SLEBase<ViewT, ltDID>
{
public:
using Base = SLEBase<ViewT, ltDID>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DIDEntry(
AccountID const& account,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::did(account), view, j)
{
}
};
using DIDEntryR = DIDEntry<ReadView>;
using DIDEntryW = DIDEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,36 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class DelegateEntry : public SLEBase<ViewT, ltDELEGATE>
{
public:
using Base = SLEBase<ViewT, ltDELEGATE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DelegateEntry(
AccountID const& account,
AccountID const& authorizedAccount,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::delegate(account, authorizedAccount), view, j)
{
}
};
using DelegateEntryR = DelegateEntry<ReadView>;
using DelegateEntryW = DelegateEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,58 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <set>
#include <utility>
namespace xrpl {
template <typename ViewT>
class DepositPreauthEntry : public SLEBase<ViewT, ltDEPOSIT_PREAUTH>
{
public:
using Base = SLEBase<ViewT, ltDEPOSIT_PREAUTH>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DepositPreauthEntry(
AccountID const& owner,
AccountID const& preauthorized,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::depositPreauth(owner, preauthorized), view, j)
{
}
explicit DepositPreauthEntry(
AccountID const& owner,
std::set<std::pair<AccountID, Slice>> const& authCreds,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::depositPreauth(owner, authCreds), view, j)
{
}
explicit DepositPreauthEntry(
uint256 const& preauthID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::depositPreauth(preauthID), view, j)
{
}
};
using DepositPreauthEntryR = DepositPreauthEntry<ReadView>;
using DepositPreauthEntryW = DepositPreauthEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,50 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class DirectoryNodeEntry : public SLEBase<ViewT, ltDIR_NODE>
{
public:
using Base = SLEBase<ViewT, ltDIR_NODE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit DirectoryNodeEntry(
AccountID const& id,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::ownerDir(id), view, j)
{
}
/**
* Resolve a specific page of the directory rooted at @p root.
*/
explicit DirectoryNodeEntry(
uint256 const& root,
std::uint64_t index,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::page(root, index), view, j)
{
}
};
using DirectoryNodeEntryR = DirectoryNodeEntry<ReadView>;
using DirectoryNodeEntryW = DirectoryNodeEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class EscrowEntry : public SLEBase<ViewT, ltESCROW>
{
public:
using Base = SLEBase<ViewT, ltESCROW>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit EscrowEntry(
AccountID const& src,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::escrow(src, seq), view, j)
{
}
};
using EscrowEntryR = EscrowEntry<ReadView>;
using EscrowEntryW = EscrowEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class FeeSettingsEntry : public SLEBase<ViewT, ltFEE_SETTINGS>
{
public:
using Base = SLEBase<ViewT, ltFEE_SETTINGS>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit FeeSettingsEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::feeSettings(), view, j)
{
}
};
using FeeSettingsEntryR = FeeSettingsEntry<ReadView>;
using FeeSettingsEntryW = FeeSettingsEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class LedgerHashesEntry : public SLEBase<ViewT, ltLEDGER_HASHES>
{
public:
using Base = SLEBase<ViewT, ltLEDGER_HASHES>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit LedgerHashesEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::skip(), view, j)
{
}
};
using LedgerHashesEntryR = LedgerHashesEntry<ReadView>;
using LedgerHashesEntryW = LedgerHashesEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class LoanBrokerEntry : public SLEBase<ViewT, ltLOAN_BROKER>
{
public:
using Base = SLEBase<ViewT, ltLOAN_BROKER>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit LoanBrokerEntry(
AccountID const& owner,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loanBroker(owner, seq), view, j)
{
}
explicit LoanBrokerEntry(
uint256 const& loanBrokerID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loanBroker(loanBrokerID), view, j)
{
}
};
using LoanBrokerEntryR = LoanBrokerEntry<ReadView>;
using LoanBrokerEntryW = LoanBrokerEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class LoanEntry : public SLEBase<ViewT, ltLOAN>
{
public:
using Base = SLEBase<ViewT, ltLOAN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit LoanEntry(
uint256 const& loanBrokerID,
SeqProxy const& loanSeq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loan(loanBrokerID, loanSeq), view, j)
{
}
explicit LoanEntry(
uint256 const& loanID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::loan(loanID), view, j)
{
}
};
using LoanEntryR = LoanEntry<ReadView>;
using LoanEntryW = LoanEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,55 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/UintTypes.h>
namespace xrpl {
template <typename ViewT>
class MPTokenEntry : public SLEBase<ViewT, ltMPTOKEN>
{
public:
using Base = SLEBase<ViewT, ltMPTOKEN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit MPTokenEntry(
MPTID const& issuanceID,
AccountID const& holder,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptoken(issuanceID, holder), view, j)
{
}
explicit MPTokenEntry(
uint256 const& issuanceKey,
AccountID const& holder,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptoken(issuanceKey, holder), view, j)
{
}
explicit MPTokenEntry(
uint256 const& mptokenKey,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptoken(mptokenKey), view, j)
{
}
};
using MPTokenEntryR = MPTokenEntry<ReadView>;
using MPTokenEntryW = MPTokenEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,56 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class MPTokenIssuanceEntry : public SLEBase<ViewT, ltMPTOKEN_ISSUANCE>
{
public:
using Base = SLEBase<ViewT, ltMPTOKEN_ISSUANCE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit MPTokenIssuanceEntry(
std::uint32_t seq,
AccountID const& issuer,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptokenIssuance(makeMptID(seq, issuer)), view, j)
{
}
explicit MPTokenIssuanceEntry(
MPTID const& issuanceID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptokenIssuance(issuanceID), view, j)
{
}
explicit MPTokenIssuanceEntry(
uint256 const& issuanceKey,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::mptokenIssuance(issuanceKey), view, j)
{
}
};
using MPTokenIssuanceEntryR = MPTokenIssuanceEntry<ReadView>;
using MPTokenIssuanceEntryW = MPTokenIssuanceEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class NFTokenOfferEntry : public SLEBase<ViewT, ltNFTOKEN_OFFER>
{
public:
using Base = SLEBase<ViewT, ltNFTOKEN_OFFER>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit NFTokenOfferEntry(
AccountID const& owner,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::nftokenOffer(owner, seq), view, j)
{
}
explicit NFTokenOfferEntry(
uint256 const& offerID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::nftokenOffer(offerID), view, j)
{
}
};
using NFTokenOfferEntryR = NFTokenOfferEntry<ReadView>;
using NFTokenOfferEntryW = NFTokenOfferEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class NFTokenPageEntry : public SLEBase<ViewT, ltNFTOKEN_PAGE>
{
public:
using Base = SLEBase<ViewT, ltNFTOKEN_PAGE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit NFTokenPageEntry(
Keylet const& page,
uint256 const& token,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::nftokenPage(page, token), view, j)
{
}
};
using NFTokenPageEntryR = NFTokenPageEntry<ReadView>;
using NFTokenPageEntryW = NFTokenPageEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class NegativeUNLEntry : public SLEBase<ViewT, ltNEGATIVE_UNL>
{
public:
using Base = SLEBase<ViewT, ltNEGATIVE_UNL>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit NegativeUNLEntry(
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::negativeUNL(), view, j)
{
}
};
using NegativeUNLEntryR = NegativeUNLEntry<ReadView>;
using NegativeUNLEntryW = NegativeUNLEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class OfferEntry : public SLEBase<ViewT, ltOFFER>
{
public:
using Base = SLEBase<ViewT, ltOFFER>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit OfferEntry(
AccountID const& id,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::offer(id, seq), view, j)
{
}
explicit OfferEntry(
uint256 const& offerID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::offer(offerID), view, j)
{
}
};
using OfferEntryR = OfferEntry<ReadView>;
using OfferEntryW = OfferEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class OracleEntry : public SLEBase<ViewT, ltORACLE>
{
public:
using Base = SLEBase<ViewT, ltORACLE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit OracleEntry(
AccountID const& account,
std::uint32_t documentID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::oracle(account, documentID), view, j)
{
}
};
using OracleEntryR = OracleEntry<ReadView>;
using OracleEntryW = OracleEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class PayChannelEntry : public SLEBase<ViewT, ltPAYCHAN>
{
public:
using Base = SLEBase<ViewT, ltPAYCHAN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit PayChannelEntry(
AccountID const& src,
AccountID const& dst,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::payChannel(src, dst, seq), view, j)
{
}
};
using PayChannelEntryR = PayChannelEntry<ReadView>;
using PayChannelEntryW = PayChannelEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class PermissionedDomainEntry : public SLEBase<ViewT, ltPERMISSIONED_DOMAIN>
{
public:
using Base = SLEBase<ViewT, ltPERMISSIONED_DOMAIN>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit PermissionedDomainEntry(
AccountID const& account,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::permissionedDomain(account, seq), view, j)
{
}
explicit PermissionedDomainEntry(
uint256 const& domainID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::permissionedDomain(domainID), view, j)
{
}
};
using PermissionedDomainEntryR = PermissionedDomainEntry<ReadView>;
using PermissionedDomainEntryW = PermissionedDomainEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,48 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/UintTypes.h>
namespace xrpl {
template <typename ViewT>
class RippleStateEntry : public SLEBase<ViewT, ltRIPPLE_STATE>
{
public:
using Base = SLEBase<ViewT, ltRIPPLE_STATE>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit RippleStateEntry(
AccountID const& id0,
AccountID const& id1,
Currency const& currency,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::trustLine(id0, id1, currency), view, j)
{
}
explicit RippleStateEntry(
AccountID const& id,
Issue const& issue,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::trustLine(id, issue), view, j)
{
}
};
using RippleStateEntryR = RippleStateEntry<ReadView>;
using RippleStateEntryW = RippleStateEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,503 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <concepts>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace xrpl {
// Concept to distinguish read-only vs writable view types
template <typename V>
concept IsWritableView = std::derived_from<V, ApplyView>;
namespace detail {
/**
* Resolves a keylet for a read-only entry.
*
* ReadView::read() on an ApplyView returns the underlying ledger's entry
* whenever the view is not already tracking one, while peek() installs the
* view's own copy and returns that. A read-only entry built with read()
* would therefore hold an SLE that goes stale the moment anything peeks the
* same key and modifies it. Resolve through peek() whenever the view really is
* an ApplyView, so every entry over that view shares one SLE.
*
* @note The const_cast is what makes reaching ApplyView::peek() possible, and
* it is defined behavior only when the view really is a non-const
* object that the caller merely observes through a const reference.
* That holds for every production view today, but it is not a
* guarantee the codebase makes: the unit tests already build
* genuinely const ApplyView-derived objects (`Sandbox const` in
* Directory_test.cpp and View_test.cpp, `PaymentSandbox const` in
* TheoreticalQuality_test.cpp and View_test.cpp). Constructing a
* read-only entry over one of those would be undefined behavior, so
* do not, until #8069 removes the cast -- by giving ApplyView a
* const-qualified peek(), which needs no amendment because
* Action::Cache is invisible to apply(), visit() and metadata.
*
* @note Consequently a "read-only" entry over an ApplyView is not free of
* side effects: peek() installs an Action::Cache entry in the apply
* state table. That is benign for transaction metadata -- Cache entries
* are skipped in ApplyStateTable::apply(), ::visit() and in metadata
* generation -- but it does cost one deep SLE copy on first touch.
*/
inline SLE::const_pointer
resolveEntry(ReadView const& view, Keylet const& key)
{
// Safe only for a view that is not itself a const object -- see the
// note above. The entry holds a const reference because it does not
// modify the view, not because the view is const.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
if (auto const applyView = dynamic_cast<ApplyView*>(const_cast<ReadView*>(&view)))
return applyView->peek(key);
return view.read(key);
}
} // namespace detail
/**
* View-parameterized base class for all ledger entries.
*
* SLEBase<ReadView> — read-only: holds shared_ptr<SLE const> + ReadView const&
* SLEBase<ApplyView> — writable: holds shared_ptr<SLE> + ApplyView& + Keylet,
* plus insert/update/erase operations
*
* Write-only members are gated by `requires` clauses, providing compile-time
* guarantees that read-only entries cannot mutate state.
*
* @tparam EntryType the ledger entry type this entry is statically bound to.
* Derived per-type entries pass their own type (e.g. ltACCOUNT_ROOT); the
* generic ReadOnlySLE / WritableSLE aliases leave it at ltANY, which opts out
* of the static type check. Binding the type here is what keeps an entry for
* one entry type from being constructed or converted from another -- see the
* converting constructor below.
*
* Derived classes should provide domain-specific accessors that hide
* implementation details of the underlying ledger entry format.
*/
template <typename ViewT, LedgerEntryType EntryType = ltANY>
class SLEBase
{
public:
static constexpr bool kIsWritable = IsWritableView<ViewT>;
// The ledger entry type this entry is bound to, and whether that binding
// is meaningful (ltANY means "any type", i.e. no static check).
static constexpr LedgerEntryType kEntryType = EntryType;
static constexpr bool kIsTyped = (EntryType != ltANY);
// SLE pointer type: mutable for writable views, const for read-only
using SlePtrType = std::conditional_t<kIsWritable, SLE::pointer, SLE::const_pointer>;
// View reference type: ApplyView& for writable, ReadView const& for
// read-only
using ViewRefType = std::conditional_t<kIsWritable, ApplyView&, ReadView const&>;
// Non-virtual by design: these entries are parameterized on the view and
// entry type, never used polymorphically through a base pointer. A vptr
// would be 8 bytes of pure overhead on a type meant to be as cheap as the
// shared_ptr it wraps. See the static_assert below the class.
//
// The destructor is public because the ReadOnlySLE / WritableSLE aliases
// name this class directly and are used as value types. Since it is not
// virtual, never delete a derived entry through an SLEBase*.
~SLEBase() = default;
SLEBase(SLEBase const&)
requires(!kIsWritable)
= default;
SLEBase(SLEBase&&) = default;
SLEBase&
operator=(SLEBase const&) = delete;
SLEBase&
operator=(SLEBase&&) = delete;
SLEBase() = delete;
// --- Constructors that adopt/resolve an SLE (public so the ReadOnlySLE /
// WritableSLE aliases and the per-type entries can be built directly
// from a keylet, or -- read-only only -- from an already-fetched
// SLE). ---
/**
* Constructor for read-only context (adopt an already-fetched SLE).
*
* There is deliberately no writable equivalent: a writable entry needs
* a Keylet so that newSLE() can still build an entry when none exists,
* and that cannot be recovered from a null SLE.
*/
explicit SLEBase(
SLE::const_pointer sle,
ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: view_(view), sle_(std::move(sle)), j_(j)
{
XRPL_ASSERT(
!kIsTyped || !sle_ || sle_->getType() == kEntryType,
"xrpl::SLEBase::SLEBase : adopted SLE matches bound entry type");
}
/**
* Constructor for read-only context (read from view by keylet)
*/
explicit SLEBase(
Keylet const& key,
ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: view_(view), sle_(detail::resolveEntry(view, key)), j_(j)
{
XRPL_ASSERT(
!kIsTyped || key.type == kEntryType,
"xrpl::SLEBase::SLEBase : keylet matches bound entry type");
}
/**
* Converting constructor: writable → read-only.
*
* Enables implicit conversion from SLEBase<ApplyView> to
* SLEBase<ReadView>, so functions taking ReadOnlySLE const& can accept
* WritableSLE.
*
* Constrained to the same entry type (or to a ltANY target, i.e. widening
* a typed entry to a generic ReadOnlySLE). The constraint is load-bearing:
* this constructor is inherited into every per-type entry, and unconstrained
* it would bind any writable entry that slices to SLEBase, so an OfferEntryW
* would convert to an AccountRootEntryR with no cast at the call site.
*/
template <typename OtherViewT, LedgerEntryType OtherType>
SLEBase(SLEBase<OtherViewT, OtherType> const& other)
requires(!kIsWritable && IsWritableView<OtherViewT> &&
(OtherType == EntryType || EntryType == ltANY))
: view_(other.readView()), sle_(other.rawSle()), j_(other.journal())
{
}
/**
* Constructor for writable context (peek from view by keylet)
*/
explicit SLEBase(
Keylet const& key,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(view), key_(key), sle_(view_.peek(key)), j_(j)
{
XRPL_ASSERT(
!kIsTyped || key.type == kEntryType,
"xrpl::SLEBase::SLEBase : keylet matches bound entry type");
}
/**
* Constructor for writable context, for call sites that hold an
* ApplyViewContext (peek from ctx.view by keylet).
*
* ctx.tx is not retained: this exists purely so transactors can pass the
* context they already have instead of spelling out ctx.view. If an entry
* ever needs the applying transaction, store it here rather than adding
* another overload.
*/
explicit SLEBase(
Keylet const& key,
ApplyViewContext const& ctx,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: SLEBase(key, ctx.view, j)
{
}
// --- Common interface (always available) ---
/**
* Returns true if the ledger entry exists
*/
[[nodiscard]] bool
exists() const
{
return sle_ != nullptr;
}
/**
* Explicit conversion to bool for convenient existence checking
*/
explicit
operator bool() const
{
return exists();
}
/**
* Returns the underlying SLE for read access.
*
* Prefer operator-> / operator* for field access; this is for the call
* sites that need the shared_ptr itself.
*/
[[nodiscard]] SLE::const_pointer
rawSle() const
{
return sle_;
}
/**
* Returns the ledger entry type of this entry.
*
* For a per-type entry this is kEntryType, known at compile time and
* valid whether or not the entry exists. Only the generic ReadOnlySLE /
* WritableSLE aliases have to read it back out of the SLE.
*
* @throws std::logic_error for a generic (ltANY) entry if exists() is
* false.
*/
[[nodiscard]] LedgerEntryType
type() const
{
if constexpr (kIsTyped)
{
return kEntryType;
}
else
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::type : entry does not exist");
return sle_->getType();
}
}
/**
* Returns the keylet identifying this entry.
*
* Writable entries keep the keylet they were built from, so it is valid
* even before newSLE(). Read-only entries derive it from the SLE, which
* must therefore exist.
*
* @throws std::logic_error for a read-only entry if exists() is false.
*/
[[nodiscard]] Keylet
keylet() const
{
if constexpr (kIsWritable)
{
return key_;
}
else
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::keylet : entry does not exist");
// Take the type from the SLE, not from kEntryType: the adopt-SLE
// constructor's type check is assert-only, so a Release build can
// be holding an SLE whose type disagrees with the binding, and the
// SLE is the one telling the truth.
return Keylet(sle_->getType(), sle_->key());
}
}
/**
* Returns the ledger key of this entry.
*
* @throws std::logic_error same as keylet(): for read-only entries,
* if exists() is false.
*/
[[nodiscard]] uint256
key() const
{
return keylet().key;
}
/**
* Returns the read view (always available; ApplyView inherits ReadView)
*/
[[nodiscard]] ReadView const&
readView() const
{
return view_;
}
/**
* Const dereference operators (always available)
*
* @throws std::logic_error if exists() is false.
*/
STLedgerEntry const*
operator->() const
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator-> : entry does not exist");
return sle_.get();
}
STLedgerEntry const&
operator*() const
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator* : entry does not exist");
return *sle_;
}
// --- Writable interface (compile-time gated) ---
//
// Everything that hands out mutable access (or mutates) is non-const, so
// that a `FooEntryW const&` is as inert as a `FooEntryR`. Use readView()
// when a const entry only needs to inspect the view.
/**
* Returns the underlying SLE for write access.
*
* Prefer operator-> / operator* for field access; this is for the call
* sites that need the shared_ptr itself.
*/
[[nodiscard]] SlePtrType const&
mutableRawSle()
requires kIsWritable
{
return sle_;
}
/**
* Returns the apply view for write operations
*/
[[nodiscard]] ApplyView&
applyView()
requires kIsWritable
{
return view_;
}
/**
* Mutable dereference operators
*
* @throws std::logic_error if exists() is false.
*/
STLedgerEntry*
operator->()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator-> : entry does not exist");
return sle_.get();
}
STLedgerEntry&
operator*()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::operator* : entry does not exist");
return *sle_;
}
/**
* Inserts the entry into the view.
*
* @throws std::logic_error if exists() is false.
*/
void
insert()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::insert : entry does not exist");
view_.insert(sle_);
}
/**
* Erases the entry from the view.
*
* Drops the SLE afterwards, so the entry reports !exists() and any
* further use throws here rather than either throwing from deep inside
* ApplyStateTable or -- worse -- silently succeeding. For an
* entry that already existed, ApplyStateTable::erase keeps holding this
* exact SLE and builds the DeletedNode's FinalFields from it, so a write
* through the entry after erase() would land in transaction metadata
* with no diagnostic at all.
*
* @throws std::logic_error if exists() is false.
*/
void
erase()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::erase : entry does not exist");
view_.erase(sle_);
sle_ = nullptr;
}
/**
* @throws std::logic_error if exists() is false.
*/
void
update()
requires kIsWritable
{
if (!exists())
Throw<std::logic_error>("xrpl::SLEBase::update : entry does not exist");
view_.update(sle_);
}
/**
* @throws std::logic_error if exists() is true: newSLE() would otherwise
* silently discard the SLE already held.
*/
void
newSLE()
requires kIsWritable
{
if (exists())
Throw<std::logic_error>("xrpl::SLEBase::newSLE : entry already exists");
sle_ = std::make_shared<SLE>(key_);
}
[[nodiscard]] beast::Journal
journal() const
{
return j_;
}
protected:
ViewRefType view_;
// Keylet is only meaningful for writable views, which need it to build an
// SLE that does not exist yet; read-only entries derive it from the SLE.
struct Empty
{
};
// No default member initializer: Keylet is not default-constructible, so
// every writable constructor must initialize key_ explicitly.
[[no_unique_address]]
std::conditional_t<kIsWritable, Keylet, Empty> key_;
SlePtrType sle_{};
beast::Journal j_;
};
/**
* Generic (any-entry-type) SLE entries.
*
* Use these when the concrete ledger entry type is not known at a given site;
* otherwise prefer the per-type entries (e.g. AccountRootEntry.h), which
* additionally enforce the entry type at compile time.
*
* SLE::const_pointer / SLE::const_ref -> ReadOnlySLE
* SLE::pointer / SLE::ref -> WritableSLE
*/
using ReadOnlySLE = SLEBase<ReadView>;
using WritableSLE = SLEBase<ApplyView>;
static_assert(
!std::is_polymorphic_v<ReadOnlySLE> && !std::is_polymorphic_v<WritableSLE>,
"SLEBase must stay a thin value type; it must not acquire a vtable");
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class SignerListEntry : public SLEBase<ViewT, ltSIGNER_LIST>
{
public:
using Base = SLEBase<ViewT, ltSIGNER_LIST>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit SignerListEntry(
AccountID const& account,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::signerList(account), view, j)
{
}
};
using SignerListEntryR = SignerListEntry<ReadView>;
using SignerListEntryW = SignerListEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,36 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
template <typename ViewT>
class SponsorshipEntry : public SLEBase<ViewT, ltSPONSORSHIP>
{
public:
using Base = SLEBase<ViewT, ltSPONSORSHIP>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit SponsorshipEntry(
AccountID const& sponsor,
AccountID const& sponsee,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::sponsorship(sponsor, sponsee), view, j)
{
}
};
using SponsorshipEntryR = SponsorshipEntry<ReadView>;
using SponsorshipEntryW = SponsorshipEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class TicketEntry : public SLEBase<ViewT, ltTICKET>
{
public:
using Base = SLEBase<ViewT, ltTICKET>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit TicketEntry(
AccountID const& id,
SeqProxy const& ticketSeq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::ticket(id, ticketSeq), view, j)
{
}
explicit TicketEntry(
uint256 const& ticketID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::ticket(ticketID), view, j)
{
}
};
using TicketEntryR = TicketEntry<ReadView>;
using TicketEntryW = TicketEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SeqProxy.h>
namespace xrpl {
template <typename ViewT>
class VaultEntry : public SLEBase<ViewT, ltVAULT>
{
public:
using Base = SLEBase<ViewT, ltVAULT>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit VaultEntry(
AccountID const& owner,
SeqProxy const& seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::vault(owner, seq), view, j)
{
}
explicit VaultEntry(
uint256 const& vaultID,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::vault(vaultID), view, j)
{
}
};
using VaultEntryR = VaultEntry<ReadView>;
using VaultEntryW = VaultEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class XChainOwnedClaimIDEntry : public SLEBase<ViewT, ltXCHAIN_OWNED_CLAIM_ID>
{
public:
using Base = SLEBase<ViewT, ltXCHAIN_OWNED_CLAIM_ID>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit XChainOwnedClaimIDEntry(
STXChainBridge const& bridge,
std::uint64_t seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::xChainClaimID(bridge, seq), view, j)
{
}
};
using XChainOwnedClaimIDEntryR = XChainOwnedClaimIDEntry<ReadView>;
using XChainOwnedClaimIDEntryW = XChainOwnedClaimIDEntry<ApplyView>;
} // namespace xrpl

View File

@@ -0,0 +1,39 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
class XChainOwnedCreateAccountClaimIDEntry
: public SLEBase<ViewT, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID>
{
public:
using Base = SLEBase<ViewT, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID>;
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using Base::Base;
explicit XChainOwnedCreateAccountClaimIDEntry(
STXChainBridge const& bridge,
std::uint64_t seq,
Base::ViewRefType view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: Base(keylet::xChainCreateAccountClaimID(bridge, seq), view, j)
{
}
};
using XChainOwnedCreateAccountClaimIDEntryR = XChainOwnedCreateAccountClaimIDEntry<ReadView>;
using XChainOwnedCreateAccountClaimIDEntryW = XChainOwnedCreateAccountClaimIDEntry<ApplyView>;
} // namespace xrpl

View File

@@ -14,6 +14,7 @@
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapLeafNode.h>
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <condition_variable>
@@ -34,7 +35,6 @@
namespace xrpl {
class SHAMapNodeID;
class SHAMapSyncFilter;
/**
@@ -420,7 +420,107 @@ public:
invariants() const;
private:
using SharedPtrNodeStack = std::stack<std::pair<SHAMapTreeNodePtr, SHAMapNodeID>>;
/**
* A path from the root of the map down to some node, pairing each node with the ID naming its
* position.
*
* The two halves of an entry must agree, and the only way to get that wrong is to compute an ID
* from the wrong branch. So this type does not accept an ID at all: every push takes the branch
* being descended and derives the ID itself, so a node and its ID cannot disagree. Reads are
* exposed through the same accessors a std::stack would offer.
*/
class NodePathStack
{
public:
[[nodiscard]] bool
empty() const
{
return stack_.empty();
}
[[nodiscard]] std::size_t
size() const
{
return stack_.size();
}
[[nodiscard]] std::pair<SHAMapTreeNodePtr, SHAMapNodeID> const&
top() const
{
XRPL_ASSERT(!stack_.empty(), "xrpl::SHAMap::NodePathStack::top : non-empty stack");
return stack_.top();
}
void
pop()
{
XRPL_ASSERT(!stack_.empty(), "xrpl::SHAMap::NodePathStack::pop : non-empty stack");
stack_.pop();
}
void
clear()
{
stack_ = {};
}
/**
* Start a path at the root of the map, whose ID is the zero-depth ID by definition.
*/
void
pushRoot(SHAMapTreeNodePtr node)
{
XRPL_ASSERT(stack_.empty(), "xrpl::SHAMap::NodePathStack::pushRoot : empty stack");
stack_.emplace(std::move(node), SHAMapNodeID{});
}
/**
* Extend the path to the child of the current node reached by `branch`.
*
* A node keeps the depth it was reached at, never a normalized kLeafDepth. Only a leaf may
* sit at kLeafDepth, since an inner node there would have no branch left to select.
*/
void
pushChild(SHAMapTreeNodePtr node, unsigned int branch)
{
XRPL_ASSERT(node, "xrpl::SHAMap::NodePathStack::pushChild : non-null node input");
XRPL_ASSERT(
!stack_.empty(), "xrpl::SHAMap::NodePathStack::pushChild : non-empty stack");
auto childID = stack_.top().second.getChildNodeID(branch);
XRPL_ASSERT_IF(
node->isInner(),
childID.getDepth() < kLeafDepth,
"xrpl::SHAMap::NodePathStack::pushChild : inner node above leaf depth");
XRPL_ASSERT_IF(
node->isLeaf(),
childID.isPrefixOf(leafKey(*node)),
"xrpl::SHAMap::NodePathStack::pushChild : leaf key below branch");
stack_.emplace(std::move(node), std::move(childID));
}
/**
* Extend the path to a node lying on the path to `target`.
*
* For nodes not reached by descending a known branch: the walk tracks only the key it is
* heading for, or the node is newly created. Either way `target` selects the branch.
*/
void
pushNode(SHAMapTreeNodePtr node, uint256 const& target)
{
if (stack_.empty())
{
pushRoot(std::move(node));
}
else
{
pushChild(std::move(node), selectBranch(stack_.top().second, target));
}
}
private:
std::stack<std::pair<SHAMapTreeNodePtr, SHAMapNodeID>> stack_;
};
using DeltaRef =
std::pair<boost::intrusive_ptr<SHAMapItem const>, boost::intrusive_ptr<SHAMapItem const>>;
@@ -447,7 +547,7 @@ private:
* Update hashes up to the root
*/
void
dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal);
dirtyUp(NodePathStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal);
/**
* Walk towards the specified id, returning the node. Caller must check
@@ -455,7 +555,7 @@ private:
* id
*/
SHAMapLeafNode*
walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack = nullptr) const;
walkTowardsKey(uint256 const& id, NodePathStack* stack = nullptr) const;
/**
* Return nullptr if key not found
*/
@@ -482,27 +582,15 @@ private:
SHAMapTreeNodePtr
writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const;
// returns the first item at or below this node
SHAMapLeafNode*
firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch = 0u) const;
// returns the last item at or below this node
SHAMapLeafNode*
lastBelow(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
unsigned int branch = kBranchFactor) const;
// direction in which belowHelper scans an inner node's branches
// direction in which a scan walks an inner node's branches
enum class BelowDirection { First, Last };
// helper function for firstBelow and lastBelow
/**
* Returns the first or last item at or below the node already on top of `stack`, extending
* `stack` with the path walked to reach it.
*/
SHAMapLeafNode*
belowHelper(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
unsigned int branch,
BelowDirection direction) const;
belowHelper(NodePathStack& stack, BelowDirection direction) const;
// Simple descent
// Get a child of the specified node
@@ -550,9 +638,9 @@ private:
hasLeafNode(uint256 const& tag, SHAMapHash const& hash) const;
SHAMapLeafNode const*
peekFirstItem(SharedPtrNodeStack& stack) const;
peekFirstItem(NodePathStack& stack) const;
SHAMapLeafNode const*
peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const;
peekNextItem(uint256 const& id, NodePathStack& stack) const;
bool
walkBranch(
SHAMapTreeNode* node,
@@ -697,7 +785,7 @@ public:
using pointer = value_type const*;
private:
SharedPtrNodeStack stack_;
NodePathStack stack_;
SHAMap const* map_ = nullptr;
pointer item_ = nullptr;
@@ -723,7 +811,7 @@ public:
private:
explicit ConstIterator(SHAMap const* map);
ConstIterator(SHAMap const* map, std::nullptr_t);
ConstIterator(SHAMap const* map, pointer item, SharedPtrNodeStack&& stack);
ConstIterator(SHAMap const* map, pointer item, NodePathStack&& stack);
friend bool
operator==(ConstIterator const& x, ConstIterator const& y);
@@ -742,10 +830,7 @@ inline SHAMap::ConstIterator::ConstIterator(SHAMap const* map, std::nullptr_t) :
{
}
inline SHAMap::ConstIterator::ConstIterator(
SHAMap const* map,
pointer item,
SharedPtrNodeStack&& stack)
inline SHAMap::ConstIterator::ConstIterator(SHAMap const* map, pointer item, NodePathStack&& stack)
: stack_(std::move(stack)), map_(map), item_(item)
{
}

View File

@@ -97,7 +97,7 @@ SHAMap::snapShot(bool isMutable) const
}
void
SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr child)
SHAMap::dirtyUp(NodePathStack& stack, uint256 const& target, SHAMapTreeNodePtr child)
{
// walk the tree up from through the inner nodes to the root_
// update hashes and links
@@ -126,29 +126,34 @@ SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNode
}
SHAMapLeafNode*
SHAMap::walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack) const
SHAMap::walkTowardsKey(uint256 const& id, NodePathStack* stack) const
{
XRPL_ASSERT(
stack == nullptr || stack->empty(), "xrpl::SHAMap::walkTowardsKey : empty stack input");
auto inNode = root_;
SHAMapNodeID nodeID;
// Every node on this walk lies on the path to `id`, so the stack can derive each ID from the
// branch `id` selects at the node above it.
auto pushCurrent = [&] {
if (stack != nullptr)
stack->pushNode(inNode, id);
};
while (inNode->isInner())
{
if (stack != nullptr)
stack->emplace(inNode, nodeID);
pushCurrent();
auto const inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(inNode);
auto& inner = safeDowncast<SHAMapInnerNode&>(*inNode);
auto const branch = selectBranch(nodeID, id);
if (inner->isEmptyBranch(branch))
if (inner.isEmptyBranch(branch))
return nullptr;
inNode = descendThrow(*inner, branch);
inNode = descendThrow(inner, branch);
nodeID = nodeID.getChildNodeID(branch);
}
if (stack != nullptr)
stack->emplace(inNode, nodeID);
pushCurrent();
return safeDowncast<SHAMapLeafNode*>(inNode.get());
}
@@ -428,65 +433,40 @@ SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, SHAMapNodeID const& nodeID)
}
SHAMapLeafNode*
SHAMap::belowHelper(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
unsigned int branch,
BelowDirection direction) const
SHAMap::belowHelper(NodePathStack& stack, BelowDirection direction) const
{
if (node->isLeaf())
{
auto n = intr_ptr::staticPointerCast<SHAMapLeafNode>(node);
stack.push({node, {kLeafDepth, n->peekItem()->key()}});
return n.get();
}
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
if (stack.empty())
{
stack.emplace(inner, SHAMapNodeID{});
}
else
{
stack.emplace(inner, stack.top().second.getChildNodeID(branch));
}
// `scanned` counts how many branches of `inner` we have examined; the branch we look at is
// derived from it, so no index ever goes out of range.
XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack input");
if (auto const& top = stack.top().first; top->isLeaf())
return safeDowncast<SHAMapLeafNode*>(top.get());
// The stack owns the node/ID pairing, so descending is only ever "push the branch we took".
// `scanned` counts how many branches of the current node we have examined; the branch we look
// at is derived from it, so no index ever goes out of range. `inner` tracks the node on top of
// the stack, which keeps it alive, so it only needs recomputing after a push.
auto* inner = safeDowncast<SHAMapInnerNode*>(stack.top().first.get());
for (auto scanned = 0u; scanned < kBranchFactor;)
{
auto const childBranch =
(direction == BelowDirection::Last) ? (kBranchFactor - 1u - scanned) : scanned;
if (!inner->isEmptyBranch(childBranch))
{
node.adopt(descendThrow(inner.get(), childBranch));
XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack");
if (node->isLeaf())
{
auto n = intr_ptr::staticPointerCast<SHAMapLeafNode>(node);
stack.push({n, {kLeafDepth, n->peekItem()->key()}});
return n.get();
}
inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
stack.emplace(inner, stack.top().second.getChildNodeID(branch));
scanned = 0u; // descend and restart the scan on the new node
}
else
if (inner->isEmptyBranch(childBranch))
{
++scanned; // scan next branch
continue;
}
stack.pushChild(descendThrow(*inner, childBranch), childBranch);
auto const& child = stack.top().first;
if (child->isLeaf())
return safeDowncast<SHAMapLeafNode*>(child.get());
inner = safeDowncast<SHAMapInnerNode*>(child.get());
scanned = 0u; // descend and restart the scan on the new node
}
return nullptr;
}
SHAMapLeafNode*
SHAMap::lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch) const
{
return belowHelper(node, stack, branch, BelowDirection::Last);
}
SHAMapLeafNode*
SHAMap::firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch) const
{
return belowHelper(node, stack, branch, BelowDirection::First);
}
static boost::intrusive_ptr<SHAMapItem const> const kNoItem;
boost::intrusive_ptr<SHAMapItem const> const&
@@ -529,36 +509,36 @@ SHAMap::onlyBelow(SHAMapTreeNode* node) const
}
SHAMapLeafNode const*
SHAMap::peekFirstItem(SharedPtrNodeStack& stack) const
SHAMap::peekFirstItem(NodePathStack& stack) const
{
XRPL_ASSERT(stack.empty(), "xrpl::SHAMap::peekFirstItem : empty stack input");
SHAMapLeafNode const* node = firstBelow(root_, stack);
stack.pushRoot(root_);
SHAMapLeafNode const* node = belowHelper(stack, BelowDirection::First);
if (node == nullptr)
{
while (!stack.empty())
stack.pop();
stack.clear();
return nullptr;
}
return node;
}
SHAMapLeafNode const*
SHAMap::peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const
SHAMap::peekNextItem(uint256 const& id, NodePathStack& stack) const
{
XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::peekNextItem : non-empty stack input");
XRPL_ASSERT(stack.top().first->isLeaf(), "xrpl::SHAMap::peekNextItem : stack starts with leaf");
stack.pop();
while (!stack.empty())
{
auto [node, nodeID] = stack.top();
auto const [node, nodeID] = stack.top();
XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::peekNextItem : another node is not leaf");
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
for (auto i = selectBranch(nodeID, id) + 1; i < kBranchFactor; ++i)
{
if (!inner->isEmptyBranch(i))
if (!inner.isEmptyBranch(i))
{
node = descendThrow(*inner, i);
auto leaf = firstBelow(node, stack, i);
stack.pushChild(descendThrow(inner, i), i);
auto leaf = belowHelper(stack, BelowDirection::First);
if (leaf == nullptr)
Throw<SHAMapMissingNode>(type_, id);
XRPL_ASSERT(leaf->isLeaf(), "xrpl::SHAMap::peekNextItem : leaf is valid");
@@ -597,11 +577,11 @@ SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const
SHAMap::ConstIterator
SHAMap::upperBound(uint256 const& id) const
{
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(id, &stack);
while (!stack.empty())
{
auto [node, nodeID] = stack.top();
auto const [node, nodeID] = stack.top();
if (node->isLeaf())
{
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
@@ -610,13 +590,13 @@ SHAMap::upperBound(uint256 const& id) const
}
else
{
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
for (auto branch = selectBranch(nodeID, id) + 1; branch < kBranchFactor; ++branch)
{
if (!inner->isEmptyBranch(branch))
if (!inner.isEmptyBranch(branch))
{
node = descendThrow(*inner, branch);
auto leaf = firstBelow(node, stack, branch);
stack.pushChild(descendThrow(inner, branch), branch);
auto leaf = belowHelper(stack, BelowDirection::First);
if (leaf == nullptr)
Throw<SHAMapMissingNode>(type_, id);
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
@@ -630,11 +610,11 @@ SHAMap::upperBound(uint256 const& id) const
SHAMap::ConstIterator
SHAMap::lowerBound(uint256 const& id) const
{
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(id, &stack);
while (!stack.empty())
{
auto [node, nodeID] = stack.top();
auto const [node, nodeID] = stack.top();
if (node->isLeaf())
{
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
@@ -643,14 +623,14 @@ SHAMap::lowerBound(uint256 const& id) const
}
else
{
auto inner = intr_ptr::staticPointerCast<SHAMapInnerNode>(node);
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
for (auto branch = selectBranch(nodeID, id); branch > 0u;)
{
--branch;
if (!inner->isEmptyBranch(branch))
if (!inner.isEmptyBranch(branch))
{
node = descendThrow(*inner, branch);
auto leaf = lastBelow(node, stack, branch);
stack.pushChild(descendThrow(inner, branch), branch);
auto leaf = belowHelper(stack, BelowDirection::Last);
if (leaf == nullptr)
Throw<SHAMapMissingNode>(type_, id);
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
@@ -675,7 +655,7 @@ SHAMap::delItem(uint256 const& id)
// delete the item with this ID
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::delItem : not immutable");
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(id, &stack);
if (stack.empty())
@@ -761,7 +741,7 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const>
// add the specified item, does not update
uint256 const tag = item->key();
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(tag, &stack);
if (stack.empty())
@@ -801,7 +781,7 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const>
while ((b1 = selectBranch(nodeID, tag)) == (b2 = selectBranch(nodeID, otherItem->key())))
{
stack.emplace(node, nodeID);
stack.pushNode(node, tag);
// we need a new inner node, since both go on same branch at this
// level
@@ -848,7 +828,7 @@ SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem cons
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::updateGiveItem : not immutable");
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(tag, &stack);
if (stack.empty())
@@ -1170,7 +1150,7 @@ SHAMap::invariants() const
auto node = root_.get();
XRPL_ASSERT(node, "xrpl::SHAMap::invariants : non-null root node");
XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::invariants : root node is not leaf");
SharedPtrNodeStack stack;
NodePathStack stack;
for (auto leaf = peekFirstItem(stack); leaf != nullptr;
leaf = peekNextItem(leaf->peekItem()->key(), stack))
;

View File

@@ -793,7 +793,7 @@ SHAMap::hasLeafNode(uint256 const& tag, SHAMapHash const& targetNodeHash) const
std::optional<std::vector<Blob>>
SHAMap::getProofPath(uint256 const& key) const
{
SharedPtrNodeStack stack;
NodePathStack stack;
walkTowardsKey(key, &stack);
if (stack.empty())

View File

@@ -8,6 +8,7 @@
#include <xrpl/shamap/SHAMapAccountStateLeafNode.h>
#include <xrpl/shamap/SHAMapInnerNode.h>
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <boost/smart_ptr/intrusive_ptr.hpp>

View File

@@ -1032,8 +1032,9 @@ public:
// makePerfLog() copies the range of names it is given, so only the names have
// to outlive the PerfLog. Here the range does not: it is destroyed before the
// counters are read. Retaining it instead is a use-after-free that a
// sanitizer build reports and this test would otherwise pass through.
// counters are read. Retaining it instead is a use-after-free, which a
// sanitizer build reports directly and which otherwise surfaces as a failed
// assertion or a Debug-mode heap-corruption abort, not a silent pass.
void
testCallerRangeNeedNotOutlive()
{

View File

@@ -31,6 +31,7 @@ set(test_modules
consensus
crypto
json
ledger
nodestore
peerfinder
protocol

View File

@@ -0,0 +1,25 @@
#include <xrpl/ledger/entries/AMMEntry.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <gtest/gtest.h>
#include <helpers/IOU.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(AMMEntryTests, Constructors)
{
EntryTestEnv e;
Asset const xrp{xrpIssue()};
Asset const usd{IOU("USD", e.alice).issue()};
expectKeylet<AMMEntry>(e, keylet::amm(xrp, usd), "amm(asset, asset)", xrp, usd);
expectKeylet<AMMEntry>(e, keylet::amm(e.someID()), "amm(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,21 @@
#include <xrpl/ledger/entries/AccountRootEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <helpers/Account.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(AccountRootEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<AccountRootEntry>(e, keylet::account(e.alice.id()), "account(id)", e.alice.id());
expectKeylet<AccountRootEntry>(
e, keylet::account(Account("nobody").id()), "account(id) absent", Account("nobody").id());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,17 @@
#include <xrpl/ledger/entries/AmendmentsEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(AmendmentsEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<AmendmentsEntry>(e, keylet::amendments(), "amendments()");
}
} // namespace xrpl::test

View File

@@ -0,0 +1,41 @@
#include <xrpl/ledger/entries/BridgeEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <gtest/gtest.h>
#include <helpers/IOU.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(BridgeEntryTests, Constructors)
{
EntryTestEnv e;
STXChainBridge const bridge{e.alice.id(), xrpIssue(), e.bob.id(), IOU("USD", e.bob).issue()};
expectKeylet<BridgeEntry>(
e,
keylet::bridge(bridge, STXChainBridge::ChainType::Locking),
"bridge(bridge, Locking)",
bridge,
STXChainBridge::ChainType::Locking);
expectKeylet<BridgeEntry>(
e,
keylet::bridge(bridge, STXChainBridge::ChainType::Issuing),
"bridge(bridge, Issuing)",
bridge,
STXChainBridge::ChainType::Issuing);
// The two chain types must not collide, or the assertions above would
// pass with chainType ignored entirely.
EXPECT_NE(
keylet::bridge(bridge, STXChainBridge::ChainType::Locking).key,
keylet::bridge(bridge, STXChainBridge::ChainType::Issuing).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,23 @@
#include <xrpl/ledger/entries/CheckEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(CheckEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(7);
expectKeylet<CheckEntry>(
e, keylet::check(e.alice.id(), seq), "check(id, seq)", e.alice.id(), seq);
expectKeylet<CheckEntry>(e, keylet::check(e.someID()), "check(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,39 @@
#include <xrpl/ledger/entries/CredentialEntry.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <string>
namespace xrpl::test {
TEST(CredentialEntryTests, Constructors)
{
EntryTestEnv e;
std::string const credTypeStr = "termsandconditions";
Slice const credType = makeSlice(credTypeStr);
expectKeylet<CredentialEntry>(
e,
keylet::credential(e.alice.id(), e.bob.id(), credType),
"credential(subject, issuer, credType)",
e.alice.id(),
e.bob.id(),
credType);
expectKeylet<CredentialEntry>(
e, keylet::credential(e.someID()), "credential(uint256)", e.someID());
// Subject and issuer are both AccountIDs, so the assertion above only
// has teeth if their order matters.
EXPECT_NE(
keylet::credential(e.alice.id(), e.bob.id(), credType).key,
keylet::credential(e.bob.id(), e.alice.id(), credType).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,17 @@
#include <xrpl/ledger/entries/DIDEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(DIDEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<DIDEntry>(e, keylet::did(e.alice.id()), "did(account)", e.alice.id());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,29 @@
#include <xrpl/ledger/entries/DelegateEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(DelegateEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<DelegateEntry>(
e,
keylet::delegate(e.alice.id(), e.bob.id()),
"delegate(account, authorizedAccount)",
e.alice.id(),
e.bob.id());
// Both arguments are AccountIDs, so the assertion above only has teeth
// if their order matters.
EXPECT_NE(
keylet::delegate(e.alice.id(), e.bob.id()).key,
keylet::delegate(e.bob.id(), e.alice.id()).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,54 @@
#include <xrpl/ledger/entries/DepositPreauthEntry.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <set>
#include <string>
#include <utility>
namespace xrpl::test {
TEST(DepositPreauthEntryTests, Constructors)
{
EntryTestEnv e;
std::string const credTypeStr = "termsandconditions";
std::set<std::pair<AccountID, Slice>> const authCreds{{e.bob.id(), makeSlice(credTypeStr)}};
expectKeylet<DepositPreauthEntry>(
e,
keylet::depositPreauth(e.alice.id(), e.bob.id()),
"depositPreauth(owner, preauthorized)",
e.alice.id(),
e.bob.id());
expectKeylet<DepositPreauthEntry>(
e,
keylet::depositPreauth(e.alice.id(), authCreds),
"depositPreauth(owner, authCreds)",
e.alice.id(),
authCreds);
expectKeylet<DepositPreauthEntry>(
e, keylet::depositPreauth(e.someID()), "depositPreauth(uint256)", e.someID());
// Owner and preauthorized are both AccountIDs, so the assertion above
// only has teeth if their order matters.
EXPECT_NE(
keylet::depositPreauth(e.alice.id(), e.bob.id()).key,
keylet::depositPreauth(e.bob.id(), e.alice.id()).key);
// The credential-set overload must not collide with the single-account
// one.
EXPECT_NE(
keylet::depositPreauth(e.alice.id(), authCreds).key,
keylet::depositPreauth(e.alice.id(), e.bob.id()).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,28 @@
#include <xrpl/ledger/entries/DirectoryNodeEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <cstdint>
namespace xrpl::test {
TEST(DirectoryNodeEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<DirectoryNodeEntry>(
e, keylet::ownerDir(e.alice.id()), "ownerDir(id)", e.alice.id());
expectKeylet<DirectoryNodeEntry>(
e, keylet::page(e.someID(), 3u), "page(root, index)", e.someID(), std::uint64_t{3});
// The two overloads reach different keylet:: functions; a copy-paste
// slip between them would be invisible otherwise.
EXPECT_NE(keylet::ownerDir(e.alice.id()).key, keylet::page(e.someID(), 3u).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,123 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <gtest/gtest.h>
#include <helpers/Account.h>
#include <helpers/TxTest.h>
#include <string>
namespace xrpl::test {
/**
* Scaffolding shared by the per-entry-type suites.
*
* Each of those suites needs the same three things: a ledger with a few funded
* accounts, a throwaway ApplyView that is never applied, and some arbitrary
* uint256 to stand in for an object ID. Build one of these per test case --
* TxTest construction dominates the runtime of these tests by a wide margin,
* and none of the assertions mutate the ledger.
*/
class EntryTestEnv
{
public:
TxTest env;
Account const alice{"alice"};
Account const bob{"bob"};
Account const carol{"carol"};
EntryTestEnv() : av_(&fundAndClose(), TapNone)
{
}
/**
* The closed ledger apply() was built over. Nothing here closes another
* ledger or submits a transaction afterward, so this and apply() never
* diverge.
*/
[[nodiscard]] ReadView const&
read() const
{
return env.getClosedLedger();
}
[[nodiscard]] ApplyView&
apply()
{
return av_;
}
/**
* An arbitrary but stable uint256, for the entry constructors that take
* an object ID directly. Nothing in the ledger has this key, which is the
* point: those overloads should resolve to a non-existent entry.
*/
[[nodiscard]] uint256
someID() const
{
return read().header().parentHash;
}
private:
// Runs from the av_ member initializer, so it may only touch env and the
// accounts -- everything declared above av_.
ReadView const&
fundAndClose()
{
env.createAccount(alice, XRP(10'000));
env.createAccount(bob, XRP(10'000));
env.createAccount(carol, XRP(10'000));
env.close();
return env.getClosedLedger();
}
ApplyViewImpl av_;
};
/**
* Assert that both flavors of @p Entry built from @p args resolve the ledger
* object that @p expected names.
*
* The entry classes are near identical, so the defect they invite is a
* copy-paste one: a constructor that reaches the wrong keylet:: function, or
* that transposes two same-typed arguments. Comparing against an independently
* spelled-out keylet at the call site catches exactly that.
*
* @p what names the overload under test, so a failure says which one broke.
*/
template <template <typename> class Entry, typename... Args>
void
expectKeylet(EntryTestEnv& e, Keylet const& expected, std::string const& what, Args const&... args)
{
bool const present = e.read().read(expected) != nullptr;
// The writable entry retains its keylet, so it can be inspected whether
// or not the entry exists.
Entry<ApplyView> const w(args..., e.apply());
EXPECT_EQ(w.keylet().key, expected.key) << what << ": writable key";
EXPECT_EQ(w.keylet().type, expected.type) << what << ": writable type";
EXPECT_EQ(w.exists(), present) << what << ": writable exists";
// The read-only entry has no keylet of its own -- it derives one from the
// SLE, and only when the SLE exists. Its agreement with a direct read
// of the expected keylet is what shows it resolved the same key.
Entry<ReadView> const r(args..., e.read());
EXPECT_EQ(r.exists(), present) << what << ": read-only exists";
if (present)
{
EXPECT_EQ(r.key(), expected.key) << what << ": read-only key";
}
// Not r.type(): for a typed entry that returns kEntryType, so checking it
// would just be this same assertion spelled twice.
static_assert(Entry<ReadView>::kEntryType == Entry<ApplyView>::kEntryType);
EXPECT_EQ(Entry<ReadView>::kEntryType, expected.type) << what << ": kEntryType";
}
} // namespace xrpl::test

View File

@@ -0,0 +1,21 @@
#include <xrpl/ledger/entries/EscrowEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(EscrowEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(11);
expectKeylet<EscrowEntry>(
e, keylet::escrow(e.alice.id(), seq), "escrow(src, seq)", e.alice.id(), seq);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,17 @@
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(FeeSettingsEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<FeeSettingsEntry>(e, keylet::feeSettings(), "feeSettings()");
}
} // namespace xrpl::test

View File

@@ -0,0 +1,17 @@
#include <xrpl/ledger/entries/LedgerHashesEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(LedgerHashesEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<LedgerHashesEntry>(e, keylet::skip(), "skip()");
}
} // namespace xrpl::test

View File

@@ -0,0 +1,24 @@
#include <xrpl/ledger/entries/LoanBrokerEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(LoanBrokerEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(5);
expectKeylet<LoanBrokerEntry>(
e, keylet::loanBroker(e.alice.id(), seq), "loanBroker(owner, seq)", e.alice.id(), seq);
expectKeylet<LoanBrokerEntry>(
e, keylet::loanBroker(e.someID()), "loanBroker(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,29 @@
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(LoanEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(9);
expectKeylet<LoanEntry>(
e, keylet::loan(e.someID(), seq), "loan(loanBrokerID, loanSeq)", e.someID(), seq);
expectKeylet<LoanEntry>(e, keylet::loan(e.someID()), "loan(uint256)", e.someID());
// Both overloads start with the same uint256, so they must not produce
// the same key -- otherwise arity is the only thing keeping them apart
// and the test proves nothing.
EXPECT_NE(keylet::loan(e.someID(), seq).key, keylet::loan(e.someID()).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,34 @@
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/UintTypes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(MPTokenEntryTests, Constructors)
{
EntryTestEnv e;
MPTID const issuanceID = makeMptID(1, e.alice.id());
expectKeylet<MPTokenEntry>(
e,
keylet::mptoken(issuanceID, e.bob.id()),
"mptoken(MPTID, holder)",
issuanceID,
e.bob.id());
expectKeylet<MPTokenEntry>(
e,
keylet::mptoken(e.someID(), e.bob.id()),
"mptoken(issuanceKey, holder)",
e.someID(),
e.bob.id());
expectKeylet<MPTokenEntry>(e, keylet::mptoken(e.someID()), "mptoken(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,33 @@
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/UintTypes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <cstdint>
namespace xrpl::test {
TEST(MPTokenIssuanceEntryTests, Constructors)
{
EntryTestEnv e;
MPTID const issuanceID = makeMptID(1, e.alice.id());
expectKeylet<MPTokenIssuanceEntry>(
e,
keylet::mptokenIssuance(makeMptID(1, e.alice.id())),
"mptokenIssuance(seq, issuer)",
std::uint32_t{1},
e.alice.id());
expectKeylet<MPTokenIssuanceEntry>(
e, keylet::mptokenIssuance(issuanceID), "mptokenIssuance(MPTID)", issuanceID);
expectKeylet<MPTokenIssuanceEntry>(
e, keylet::mptokenIssuance(e.someID()), "mptokenIssuance(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,24 @@
#include <xrpl/ledger/entries/NFTokenOfferEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(NFTokenOfferEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(13);
expectKeylet<NFTokenOfferEntry>(
e, keylet::nftokenOffer(e.alice.id(), seq), "nftokenOffer(owner, seq)", e.alice.id(), seq);
expectKeylet<NFTokenOfferEntry>(
e, keylet::nftokenOffer(e.someID()), "nftokenOffer(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,25 @@
#include <xrpl/ledger/entries/NFTokenPageEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(NFTokenPageEntryTests, Constructors)
{
EntryTestEnv e;
Keylet const pageMin = keylet::nftokenPageMin(e.alice.id());
expectKeylet<NFTokenPageEntry>(
e,
keylet::nftokenPage(pageMin, e.someID()),
"nftokenPage(page, token)",
pageMin,
e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,17 @@
#include <xrpl/ledger/entries/NegativeUNLEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(NegativeUNLEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<NegativeUNLEntry>(e, keylet::negativeUNL(), "negativeUNL()");
}
} // namespace xrpl::test

View File

@@ -0,0 +1,23 @@
#include <xrpl/ledger/entries/OfferEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(OfferEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(3);
expectKeylet<OfferEntry>(
e, keylet::offer(e.alice.id(), seq), "offer(id, seq)", e.alice.id(), seq);
expectKeylet<OfferEntry>(e, keylet::offer(e.someID()), "offer(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,24 @@
#include <xrpl/ledger/entries/OracleEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <cstdint>
namespace xrpl::test {
TEST(OracleEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<OracleEntry>(
e,
keylet::oracle(e.alice.id(), 7u),
"oracle(account, documentID)",
e.alice.id(),
std::uint32_t{7});
}
} // namespace xrpl::test

View File

@@ -0,0 +1,33 @@
#include <xrpl/ledger/entries/PayChannelEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(PayChannelEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(4);
expectKeylet<PayChannelEntry>(
e,
keylet::payChannel(e.alice.id(), e.bob.id(), seq),
"payChannel(src, dst, seq)",
e.alice.id(),
e.bob.id(),
seq);
// Source and destination are both AccountIDs, so the assertion above
// only has teeth if their order matters.
EXPECT_NE(
keylet::payChannel(e.alice.id(), e.bob.id(), seq).key,
keylet::payChannel(e.bob.id(), e.alice.id(), seq).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,28 @@
#include <xrpl/ledger/entries/PermissionedDomainEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(PermissionedDomainEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(6);
expectKeylet<PermissionedDomainEntry>(
e,
keylet::permissionedDomain(e.alice.id(), seq),
"permissionedDomain(account, seq)",
e.alice.id(),
seq);
expectKeylet<PermissionedDomainEntry>(
e, keylet::permissionedDomain(e.someID()), "permissionedDomain(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,43 @@
#include <xrpl/ledger/entries/RippleStateEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/UintTypes.h>
#include <gtest/gtest.h>
#include <helpers/IOU.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(RippleStateEntryTests, Constructors)
{
EntryTestEnv e;
IOU const usd("USD", e.alice);
Currency const currency = usd.currency();
expectKeylet<RippleStateEntry>(
e,
keylet::trustLine(e.alice.id(), e.bob.id(), currency),
"trustLine(id0, id1, currency)",
e.alice.id(),
e.bob.id(),
currency);
expectKeylet<RippleStateEntry>(
e,
keylet::trustLine(e.bob.id(), usd.issue()),
"trustLine(id, issue)",
e.bob.id(),
usd.issue());
// Trust lines are deliberately symmetric in their two accounts -- the
// keylet canonicalizes them -- so unlike the other two-account entries
// there is no transposition to catch here.
EXPECT_EQ(
keylet::trustLine(e.alice.id(), e.bob.id(), currency).key,
keylet::trustLine(e.bob.id(), e.alice.id(), currency).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,428 @@
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/AMMEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/AccountRootEntry.h>
#include <xrpl/ledger/entries/AmendmentsEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/BridgeEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/CheckEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/CredentialEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/DIDEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/DelegateEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/DepositPreauthEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/DirectoryNodeEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/EscrowEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/FeeSettingsEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/LedgerHashesEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/LoanBrokerEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/LoanEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/MPTokenEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/NFTokenOfferEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/NFTokenPageEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/NegativeUNLEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/OfferEntry.h>
#include <xrpl/ledger/entries/OracleEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/PayChannelEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/PermissionedDomainEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/RippleStateEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/SignerListEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/SponsorshipEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/TicketEntry.h>
#include <xrpl/ledger/entries/VaultEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
#include <gtest/gtest.h>
#include <helpers/Account.h>
#include <helpers/TxTest.h>
#include <stdexcept>
#include <tuple>
#include <type_traits>
namespace xrpl {
// The entry classes have no consumers yet, and an un-instantiated class
// template is barely type-checked. Instantiate every one explicitly so the
// compiler actually checks them. Keep this block even once real call sites
// exist: it is what catches a new ledger entry type being added without its
// wrapper class, or the wrapper class existing but never actually being used.
//
// Driving this off ledger_entries.macro keeps it exhaustive by construction:
// adding a ledger entry type without adding its entry class stops compiling
// here, and the static_assert pins each one to the right LedgerEntryType.
//
// Keep this loop in one file rather than splitting it across the per-entry
// *Entry_test.cpp suites. Those are hand-written, so a new ledger entry type
// would simply have no file there and nothing would complain; this is the only
// thing making the coverage exhaustive rather than merely extensive.
template class SLEBase<ReadView>;
template class SLEBase<ApplyView>;
#pragma push_macro("LEDGER_ENTRY")
#undef LEDGER_ENTRY
#define LEDGER_ENTRY(tag, value, name, ...) \
template class name##Entry<ReadView>; \
template class name##Entry<ApplyView>; \
static_assert( \
name##Entry<ReadView>::kEntryType == tag && name##Entry<ApplyView>::kEntryType == tag, \
#name "Entry must be bound to " #tag);
#include <xrpl/protocol/detail/ledger_entries.macro>
#undef LEDGER_ENTRY
#pragma pop_macro("LEDGER_ENTRY")
// --- Entry-type safety, checked at compile time. ---
//
// The writable -> read-only converting constructor is inherited into every
// per-type entry, so without the entry-type constraint it will bind any
// writable entry that slices to SLEBase. These assertions pin down which
// conversions are legal.
// An entry class for one entry type must never be constructible from another.
static_assert(
!std::is_convertible_v<OfferEntryW, AccountRootEntryR>,
"cross-entry-type conversion must not compile");
static_assert(
!std::is_constructible_v<AccountRootEntryR, OfferEntryW>,
"cross-entry-type construction must not compile, even explicitly");
static_assert(
!std::is_convertible_v<OfferEntryR, AccountRootEntryR>,
"read-only cross-entry-type conversion must not compile");
// Nor from a type-erased writable entry, which carries no static type.
static_assert(
!std::is_convertible_v<WritableSLE, AccountRootEntryR>,
"generic -> typed conversion must not compile");
// The intended conversions must keep working: same type writable -> read-only,
// and typed -> generic widening.
static_assert(
std::is_convertible_v<AccountRootEntryW, AccountRootEntryR>,
"same-type writable -> read-only conversion must keep working");
static_assert(
std::is_convertible_v<AccountRootEntryW, ReadOnlySLE>,
"typed -> generic widening must keep working");
// Detection idioms for the writable interface. These have to go through a
// template parameter: a requires-expression over a concrete type is checked
// eagerly, so spelling the calls out inline would be a hard error rather than
// the `false` the assertions below want.
template <typename T>
concept HasMutableRawSle = requires(T& t) { t.mutableRawSle(); };
template <typename T>
concept HasApplyView = requires(T& t) { t.applyView(); };
namespace test {
/**
* Scaffolding shared by the test cases below: a funded alice, an unfunded bob
* (for the entries that need to resolve to nothing), and the TxTest ledger
* they live in.
*/
class SLEBaseTests : public ::testing::Test
{
protected:
TxTest env_;
Account const alice_{"alice"};
Account const bob_{"bob"};
SLEBaseTests()
{
env_.createAccount(alice_, XRP(10'000));
}
};
TEST_F(SLEBaseTests, ReadOnly)
{
AccountRootEntryR const absent(bob_.id(), env_.getClosedLedger());
EXPECT_FALSE(absent.exists());
EXPECT_FALSE(static_cast<bool>(absent));
// A typed entry knows its entry type even with nothing to read.
EXPECT_EQ(absent.type(), ltACCOUNT_ROOT);
AccountRootEntryR const present(alice_.id(), env_.getClosedLedger());
EXPECT_TRUE(present.exists());
EXPECT_TRUE(static_cast<bool>(present));
EXPECT_EQ(present.key(), keylet::account(alice_.id()).key);
EXPECT_EQ(present.type(), ltACCOUNT_ROOT);
EXPECT_EQ(present.keylet().type, ltACCOUNT_ROOT);
EXPECT_EQ(present->getType(), ltACCOUNT_ROOT);
EXPECT_EQ((*present).getType(), ltACCOUNT_ROOT);
EXPECT_EQ(&present.readView(), &env_.getClosedLedger());
}
TEST_F(SLEBaseTests, AdoptSLE)
{
auto const sle = env_.getClosedLedger().read(keylet::account(alice_.id()));
ASSERT_NE(sle, nullptr);
AccountRootEntryR const adopted(sle, env_.getClosedLedger());
EXPECT_TRUE(adopted.exists());
EXPECT_EQ(adopted.rawSle(), sle);
EXPECT_EQ(adopted.key(), keylet::account(alice_.id()).key);
EXPECT_EQ(adopted.type(), ltACCOUNT_ROOT);
// keylet() reports the SLE's own type, not the entry's static binding, so
// it stays truthful in a Release build where the constructor's
// entry-type assert is compiled out.
EXPECT_EQ(adopted.keylet().type, ltACCOUNT_ROOT);
// Adopting a null SLE is allowed: the assert only fires on a
// type mismatch, and a null pointer has no type to mismatch.
AccountRootEntryR const empty(SLE::const_pointer{}, env_.getClosedLedger());
EXPECT_FALSE(empty.exists());
EXPECT_EQ(empty.type(), ltACCOUNT_ROOT);
// A generic entry adopting the same SLE has to read the type back.
ReadOnlySLE const generic(sle, env_.getClosedLedger());
EXPECT_TRUE(generic.exists());
EXPECT_EQ(generic.type(), ltACCOUNT_ROOT);
EXPECT_EQ(generic.keylet().type, ltACCOUNT_ROOT);
// There is deliberately no writable equivalent.
static_assert(
!std::is_constructible_v<AccountRootEntryW, SLE::pointer, ApplyView&>,
"writable entries must not be constructible from a bare SLE");
}
TEST_F(SLEBaseTests, WritableAccessors)
{
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
beast::Journal const j{beast::Journal::getNullSink()};
AccountRootEntryW account(alice_.id(), av, j);
EXPECT_TRUE(account.exists());
EXPECT_EQ(account.mutableRawSle(), account.rawSle());
EXPECT_EQ(&account.applyView(), &av);
EXPECT_EQ(&account.readView(), static_cast<ReadView const*>(&av));
EXPECT_EQ(&account.journal().sink(), &j.sink());
// The mutable dereference operators reach the same entry.
EXPECT_EQ(account.operator->(), account.rawSle().get());
EXPECT_EQ(&*account, account.rawSle().get());
// Everything handing out mutable access is non-const, so a const
// writable entry is as inert as a read-only one.
static_assert(HasMutableRawSle<AccountRootEntryW>);
static_assert(HasApplyView<AccountRootEntryW>);
static_assert(
!HasMutableRawSle<AccountRootEntryW const>,
"mutableRawSle() must not be callable on a const writable entry");
static_assert(
!HasApplyView<AccountRootEntryW const>,
"applyView() must not be callable on a const writable entry");
// Read-only entries do not have the writable interface at all.
static_assert(
!HasMutableRawSle<AccountRootEntryR>,
"mutableRawSle() must not exist on a read-only entry");
static_assert(
!HasApplyView<AccountRootEntryR>, "applyView() must not exist on a read-only entry");
}
TEST_F(SLEBaseTests, ApplyViewContextCtor)
{
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
beast::Journal const j{beast::Journal::getNullSink()};
transactions::AccountSetBuilder builder{alice_.id()};
builder.setSequence(env_.getAccountRoot(alice_.id()).getSequence());
builder.setFee(XRPAmount(10));
auto const tx = builder.build(alice_.pk(), alice_.sk()).getSTTx();
ASSERT_NE(tx, nullptr);
ApplyViewContext const ctx{.view = av, .tx = *tx};
// Delegates to the (Keylet, ApplyView&) constructor; ctx.tx is not
// retained, so this must be indistinguishable from building from
// ctx.view directly.
AccountRootEntryW fromCtx(keylet::account(alice_.id()), ctx, j);
EXPECT_TRUE(fromCtx.exists());
EXPECT_EQ(&fromCtx.applyView(), &av);
EXPECT_EQ(fromCtx.key(), keylet::account(alice_.id()).key);
AccountRootEntryW const fromView(keylet::account(alice_.id()), av, j);
EXPECT_EQ(fromCtx.rawSle(), fromView.rawSle());
}
TEST_F(SLEBaseTests, WritableLifecycle)
{
// A view we never apply, so nothing here reaches the ledger.
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
// Entry that does not exist yet: newSLE() -> insert().
{
TicketEntryW ticket(keylet::ticket(alice_.id(), SeqProxy::rawTicket(1)), av);
EXPECT_FALSE(ticket.exists());
EXPECT_EQ(ticket.key(), keylet::ticket(alice_.id(), SeqProxy::rawTicket(1)).key);
EXPECT_EQ(ticket.type(), ltTICKET);
EXPECT_EQ(ticket.keylet().type, ltTICKET);
ticket.newSLE();
EXPECT_TRUE(ticket.exists());
ticket.insert();
ticket.update();
// Erasing an entry inserted in this same view drops it outright.
ticket.erase();
EXPECT_FALSE(ticket.exists());
}
// Entry that already exists: update() is what promotes it from a bare
// peek to a real change. ApplyViewImpl::size() counts Insert, Modify and
// Erase but not Cache, so it shows the difference: building the entry
// only peeks, and the write is invisible to the view until update().
{
ApplyViewImpl fresh(&env_.getClosedLedger(), TapNone);
AccountRootEntryW account(alice_.id(), fresh);
EXPECT_TRUE(account.exists());
EXPECT_EQ(fresh.size(), 0);
account->setFieldU32(sfSequence, account->getFieldU32(sfSequence) + 1);
EXPECT_EQ(fresh.size(), 0);
account.update();
EXPECT_EQ(fresh.size(), 1);
// update() is idempotent: the entry is already a Modify.
account.update();
EXPECT_EQ(fresh.size(), 1);
}
// Entry that already exists. ApplyStateTable::erase() keeps holding
// this exact SLE and builds the DeletedNode's FinalFields from it, so
// the entry must drop its pointer or a later write would silently
// land in transaction metadata.
{
AccountRootEntryW account(alice_.id(), av);
EXPECT_TRUE(account.exists());
account.erase();
EXPECT_FALSE(account.exists());
}
}
TEST_F(SLEBaseTests, Conversion)
{
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
AccountRootEntryW const writable(alice_.id(), av);
EXPECT_TRUE(writable.exists());
AccountRootEntryR const readOnly = writable;
EXPECT_TRUE(readOnly.exists());
EXPECT_EQ(readOnly.rawSle(), writable.rawSle());
ReadOnlySLE const generic = writable;
EXPECT_TRUE(generic.exists());
EXPECT_EQ(generic.rawSle(), writable.rawSle());
// A generic entry has to read the type back out of the SLE.
EXPECT_EQ(generic.type(), ltACCOUNT_ROOT);
}
TEST_F(SLEBaseTests, ResolveEntryPeeks)
{
// getOpenLedger() is an OpenView, which derives from ReadView but not
// from ApplyView, so resolveEntry's dynamic_cast fails and this takes
// the plain ReadView::read() path.
OpenView const& ledger = env_.getOpenLedger();
AccountRootEntryR const overLedger(alice_.id(), ledger);
EXPECT_TRUE(overLedger.exists());
ApplyViewImpl av(&ledger, TapNone);
// ReadView const& binds an ApplyViewImpl just as happily, and there the
// dynamic_cast succeeds, so this one resolves through ApplyView::peek().
AccountRootEntryR const readOnly(alice_.id(), av);
EXPECT_TRUE(readOnly.exists());
AccountRootEntryW writable(alice_.id(), av);
EXPECT_TRUE(writable.exists());
// The invariant resolveEntry() exists to hold: one SLE per key per
// view. read() would have handed back the base ledger's entry instead,
// which is a different object.
EXPECT_EQ(readOnly.rawSle(), writable.rawSle());
EXPECT_NE(readOnly.rawSle(), overLedger.rawSle());
// Which is what keeps a read-only entry from going stale: a write
// through any other entry over the same view is visible through it.
auto const bumped = writable->getFieldU32(sfSequence) + 1;
writable->setFieldU32(sfSequence, bumped);
EXPECT_EQ(readOnly->getFieldU32(sfSequence), bumped);
}
TEST_F(SLEBaseTests, ThrowsOnMissingEntry)
{
// A generic read-only entry has no static type to fall back on, so
// type() must read it off the (absent) SLE and throw.
ReadOnlySLE const absent(keylet::account(bob_.id()), env_.getClosedLedger());
EXPECT_FALSE(absent.exists());
EXPECT_THROW(std::ignore = absent.type(), std::logic_error);
// A per-type read-only entry always knows its type, but keylet() and
// key() still have to derive the ledger key from the SLE.
AccountRootEntryR const missing(bob_.id(), env_.getClosedLedger());
EXPECT_FALSE(missing.exists());
EXPECT_THROW(std::ignore = missing.key(), std::logic_error);
EXPECT_THROW(std::ignore = missing.keylet(), std::logic_error);
// Dereferencing an absent entry throws rather than handing back a null
// pointer for the caller to walk into.
EXPECT_THROW(std::ignore = missing.operator->(), std::logic_error);
EXPECT_THROW(std::ignore = (*missing).getType(), std::logic_error);
}
TEST_F(SLEBaseTests, ThrowsOnMissingWritableEntry)
{
// A view we never apply, so nothing here reaches the ledger.
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
// bob is unfunded, so this resolves to nothing and every operation that
// needs an SLE has to throw instead of dereferencing null. These are the
// cases a Release build used to walk straight past, back when they were
// XRPL_ASSERTs.
AccountRootEntryW missing(bob_.id(), av);
EXPECT_FALSE(missing.exists());
EXPECT_THROW(std::ignore = missing.operator->(), std::logic_error);
EXPECT_THROW(std::ignore = (*missing).getType(), std::logic_error);
EXPECT_THROW(missing.insert(), std::logic_error);
EXPECT_THROW(missing.update(), std::logic_error);
EXPECT_THROW(missing.erase(), std::logic_error);
// keylet() and key() are the exception: a writable entry keeps the keylet
// it was built from, so they stay valid before newSLE().
EXPECT_EQ(missing.key(), keylet::account(bob_.id()).key);
// newSLE() is the inverse -- it throws when the entry *does* exist,
// rather than silently dropping the SLE already held.
missing.newSLE();
EXPECT_TRUE(missing.exists());
EXPECT_THROW(missing.newSLE(), std::logic_error);
// And once erased, the entry is empty again and throws as before.
missing.insert();
missing.erase();
EXPECT_FALSE(missing.exists());
EXPECT_THROW(missing.update(), std::logic_error);
}
} // namespace test
} // namespace xrpl

View File

@@ -0,0 +1,18 @@
#include <xrpl/ledger/entries/SignerListEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(SignerListEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<SignerListEntry>(
e, keylet::signerList(e.alice.id()), "signerList(account)", e.alice.id());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,29 @@
#include <xrpl/ledger/entries/SponsorshipEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(SponsorshipEntryTests, Constructors)
{
EntryTestEnv e;
expectKeylet<SponsorshipEntry>(
e,
keylet::sponsorship(e.alice.id(), e.bob.id()),
"sponsorship(sponsor, sponsee)",
e.alice.id(),
e.bob.id());
// Sponsor and sponsee are both AccountIDs, so the assertion above only
// has teeth if their order matters.
EXPECT_NE(
keylet::sponsorship(e.alice.id(), e.bob.id()).key,
keylet::sponsorship(e.bob.id(), e.alice.id()).key);
}
} // namespace xrpl::test

View File

@@ -0,0 +1,27 @@
#include <xrpl/ledger/entries/TicketEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(TicketEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const ticketSeq = SeqProxy::rawTicket(2);
expectKeylet<TicketEntry>(
e,
keylet::ticket(e.alice.id(), ticketSeq),
"ticket(id, ticketSeq)",
e.alice.id(),
ticketSeq);
expectKeylet<TicketEntry>(e, keylet::ticket(e.someID()), "ticket(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,23 @@
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
TEST(VaultEntryTests, Constructors)
{
EntryTestEnv e;
SeqProxy const seq = SeqProxy::rawSequence(8);
expectKeylet<VaultEntry>(
e, keylet::vault(e.alice.id(), seq), "vault(owner, seq)", e.alice.id(), seq);
expectKeylet<VaultEntry>(e, keylet::vault(e.someID()), "vault(uint256)", e.someID());
}
} // namespace xrpl::test

View File

@@ -0,0 +1,29 @@
#include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <gtest/gtest.h>
#include <helpers/IOU.h>
#include <ledger/EntryTestHelpers.h>
#include <cstdint>
namespace xrpl::test {
TEST(XChainOwnedClaimIDEntryTests, Constructors)
{
EntryTestEnv e;
STXChainBridge const bridge{e.alice.id(), xrpIssue(), e.bob.id(), IOU("USD", e.bob).issue()};
expectKeylet<XChainOwnedClaimIDEntry>(
e,
keylet::xChainClaimID(bridge, 5u),
"xChainClaimID(bridge, seq)",
bridge,
std::uint64_t{5});
}
} // namespace xrpl::test

View File

@@ -0,0 +1,35 @@
#include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <gtest/gtest.h>
#include <helpers/IOU.h>
#include <ledger/EntryTestHelpers.h>
#include <cstdint>
namespace xrpl::test {
TEST(XChainOwnedCreateAccountClaimIDEntryTests, Constructors)
{
EntryTestEnv e;
STXChainBridge const bridge{e.alice.id(), xrpIssue(), e.bob.id(), IOU("USD", e.bob).issue()};
expectKeylet<XChainOwnedCreateAccountClaimIDEntry>(
e,
keylet::xChainCreateAccountClaimID(bridge, 5u),
"xChainCreateAccountClaimID(bridge, seq)",
bridge,
std::uint64_t{5});
// Must not collide with the plain claim-ID keylet, which takes the same
// arguments.
EXPECT_NE(
keylet::xChainCreateAccountClaimID(bridge, 5u).key, keylet::xChainClaimID(bridge, 5u).key);
}
} // namespace xrpl::test

View File

@@ -272,6 +272,412 @@ INSTANTIATE_TEST_SUITE_P(
::testing::Values(kBackedMode, kUnbackedMode),
shamapBackingModeName);
// Exercises the traversal stacks built by belowHelper. Each stack entry pairs a node with the ID
// naming its position, and SHAMap asserts that pairing on every push, so these traversals fail
// loudly in a Debug build if a node ID is ever derived from the wrong branch.
class SHAMapTraversal : public ::testing::Test
{
protected:
beast::Journal const j_{TestSink::instance()};
// Keys that share a long prefix and then fan out across distinct branches, so the deeper inner
// nodes have several children and traversal must descend many levels.
static std::vector<uint256>
deepFanOutKeys()
{
std::vector<uint256> keys;
for (unsigned int branch = 0; branch < SHAMap::kBranchFactor; ++branch)
{
// Vary the 6th nibble, keeping the first five identical.
auto text = std::string("abcde") + "0123456789abcdef"[branch];
text.append(64 - text.size(), '7');
keys.emplace_back(std::string_view{text});
}
return keys;
}
// Keys that share all 63 leading nibbles and fan out only at the last one, so the tree is a
// chain of single-child inner nodes down to depth 63 with the leaves as siblings at depth 64.
// This exercises kLeafDepth directly, unlike deepFanOutKeys() above, whose fan-out at the 6th
// nibble keeps the tree only about 6 levels deep.
static std::vector<uint256>
deepFanOutKeysAtLeafDepth()
{
std::vector<uint256> keys;
for (unsigned int branch = 0; branch < SHAMap::kBranchFactor; ++branch)
{
auto text = std::string(63, 'a') + "0123456789abcdef"[branch];
keys.emplace_back(std::string_view{text});
}
return keys;
}
static void
fillMap(SHAMap& map, std::vector<uint256> const& keys)
{
map.setUnbacked();
for (auto const& k : keys)
{
Buffer vuc{32};
std::fill_n(vuc.data(), vuc.size(), std::uint8_t{1});
EXPECT_TRUE(
map.addItem(SHAMapNodeType::TnAccountState, makeShamapitem(k, std::move(vuc))));
map.invariants();
}
}
};
TEST_F(SHAMapTraversal, forward_iteration_visits_every_key_in_order)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeys();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
std::vector<uint256> visited;
for (auto const& item : map)
visited.push_back(item.key());
EXPECT_EQ(visited, keys);
}
TEST_F(SHAMapTraversal, upper_bound_walks_the_whole_map)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeys();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// upperBound from each key must land on its successor, driving belowHelper across every
// subtree.
for (std::size_t k = 0; k + 1 < keys.size(); ++k)
{
auto it = map.upperBound(keys[k]);
ASSERT_NE(it, map.end()) << "no successor for key " << k;
EXPECT_EQ(it->key(), keys[k + 1]) << "wrong successor for key " << k;
}
EXPECT_EQ(map.upperBound(keys.back()), map.end());
}
TEST_F(SHAMapTraversal, lower_bound_walks_the_whole_map)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeys();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// lowerBound is the reverse direction: belowHelper descends to the greatest key below a
// subtree.
for (std::size_t k = 1; k < keys.size(); ++k)
{
auto it = map.lowerBound(keys[k]);
ASSERT_NE(it, map.end()) << "no predecessor for key " << k;
EXPECT_EQ(it->key(), keys[k - 1]) << "wrong predecessor for key " << k;
}
EXPECT_EQ(map.lowerBound(keys.front()), map.end());
}
TEST_F(SHAMapTraversal, bounds_agree_with_iteration_for_absent_keys)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeys();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// Probe keys that are not in the map, so the traversal starts mid-tree rather than at a leaf.
for (unsigned char const c : {0x00, 0x40, 0x80, 0xc0, 0xff})
{
uint256 probe;
std::fill_n(probe.begin(), probe.size(), c);
auto const expectedUpper = std::ranges::upper_bound(keys, probe);
auto const upper = map.upperBound(probe);
if (expectedUpper == keys.end())
{
EXPECT_EQ(upper, map.end()) << "probe " << static_cast<unsigned>(c);
}
else
{
ASSERT_NE(upper, map.end()) << "probe " << static_cast<unsigned>(c);
EXPECT_EQ(upper->key(), *expectedUpper) << "probe " << static_cast<unsigned>(c);
}
auto const lowerCount = std::ranges::lower_bound(keys, probe) - keys.begin();
auto const lower = map.lowerBound(probe);
if (lowerCount == 0)
{
EXPECT_EQ(lower, map.end()) << "probe " << static_cast<unsigned>(c);
}
else
{
ASSERT_NE(lower, map.end()) << "probe " << static_cast<unsigned>(c);
EXPECT_EQ(lower->key(), keys[lowerCount - 1]) << "probe " << static_cast<unsigned>(c);
}
}
// Probe keys that land on a leaf and require the leaf-pop-and-resume case. Keys share
// "abcde" as a prefix and vary the 6th nibble, so a probe that shares the full prefix
// but differs in the padding hits a leaf from either side: '0' padded with '0' lands below
// the first key, and 'f' padded with 'f' lands above the last.
for (char const nibble : {'0', 'f'})
{
auto text = std::string("abcde") + nibble;
text.append(64 - text.size(), nibble);
uint256 const probe{std::string_view{text}};
auto const expectedUpper = std::ranges::upper_bound(keys, probe);
auto const upper = map.upperBound(probe);
if (expectedUpper == keys.end())
{
EXPECT_EQ(upper, map.end()) << "nibble " << nibble;
}
else
{
ASSERT_NE(upper, map.end()) << "nibble " << nibble;
EXPECT_EQ(upper->key(), *expectedUpper) << "nibble " << nibble;
}
auto const lowerCount = std::ranges::lower_bound(keys, probe) - keys.begin();
auto const lower = map.lowerBound(probe);
if (lowerCount == 0)
{
EXPECT_EQ(lower, map.end()) << "nibble " << nibble;
}
else
{
ASSERT_NE(lower, map.end()) << "nibble " << nibble;
EXPECT_EQ(lower->key(), keys[lowerCount - 1]) << "nibble " << nibble;
}
}
}
TEST_F(SHAMapTraversal, iteration_survives_deletions)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeys();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// Deleting every other key drops the fan-out node's branch count from 16 to 8, never the 1
// that would make delItem collapse it into a leaf. So this pins that iteration survives
// deletions that reshape the map without collapsing any inner node; the case that does
// collapse one is iteration_survives_a_collapsed_inner_node below.
for (std::size_t k = 0; k < keys.size(); k += 2)
{
ASSERT_TRUE(map.delItem(keys[k]));
map.invariants();
}
std::vector<uint256> expected;
for (std::size_t k = 1; k < keys.size(); k += 2)
expected.push_back(keys[k]);
std::vector<uint256> visited;
for (auto const& item : map)
visited.push_back(item.key());
EXPECT_EQ(visited, expected);
for (std::size_t k = 0; k + 1 < expected.size(); ++k)
{
auto it = map.upperBound(expected[k]);
ASSERT_NE(it, map.end());
EXPECT_EQ(it->key(), expected[k + 1]);
}
}
TEST_F(SHAMapTraversal, iteration_survives_a_collapsed_inner_node)
{
tests::TestNodeFamily f{j_};
SHAMap map{SHAMapType::FREE, f};
// One key in a separate subtree, diverging from the fan-out group at the very first nibble, so
// it survives untouched while the fan-out group below is collapsed.
auto const sentinel = uint256{std::string_view{std::string(64, '0')}};
auto fanOutKeys = deepFanOutKeysAtLeafDepth();
fillMap(map, fanOutKeys);
Buffer vuc{32};
std::fill_n(vuc.data(), vuc.size(), std::uint8_t{1});
ASSERT_TRUE(
map.addItem(SHAMapNodeType::TnAccountState, makeShamapitem(sentinel, std::move(vuc))));
map.invariants();
std::ranges::sort(fanOutKeys);
// Delete all but the last fan-out key. The fan-out node's branch count drops to 1 on the final
// delete, which delItem collapses by pulling the sole remaining leaf up in its place; every
// ancestor above it has exactly one child by construction, so each of those also drops to
// branch count 1 and collapses in turn, all the way up to (but not including) the root. That
// final delete replaces the entire 63-level chain with the root pointing straight at the one
// remaining leaf, so the surviving traversal stack is rebuilt over a drastically different tree
// shape, not just missing one inner node.
for (std::size_t k = 0; k + 1 < fanOutKeys.size(); ++k)
{
ASSERT_TRUE(map.delItem(fanOutKeys[k]));
map.invariants();
}
std::vector<uint256> const expected{sentinel, fanOutKeys.back()};
std::vector<uint256> visited;
for (auto const& item : map)
visited.push_back(item.key());
EXPECT_EQ(visited, expected);
auto it = map.upperBound(sentinel);
ASSERT_NE(it, map.end());
EXPECT_EQ(it->key(), fanOutKeys.back());
EXPECT_EQ(map.upperBound(fanOutKeys.back()), map.end());
}
// The tests below mirror the ones above but use deepFanOutKeysAtLeafDepth(), whose keys share all
// 63 leading nibbles and fan out only at the last one. That puts the leaves at depth
// SHAMap::kLeafDepth, so these traversals walk a chain of single-child inner nodes all the way down
// and exercise the kLeafDepth guards that deepFanOutKeys() alone (fanning out at the 6th nibble)
// never reaches.
TEST_F(SHAMapTraversal, forward_iteration_visits_every_key_in_order_at_leaf_depth)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeysAtLeafDepth();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
std::vector<uint256> visited;
for (auto const& item : map)
visited.push_back(item.key());
EXPECT_EQ(visited, keys);
}
TEST_F(SHAMapTraversal, upper_bound_walks_the_whole_map_at_leaf_depth)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeysAtLeafDepth();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// upperBound from each key must land on its successor, driving belowHelper down to depth
// kLeafDepth for every subtree.
for (std::size_t k = 0; k + 1 < keys.size(); ++k)
{
auto it = map.upperBound(keys[k]);
ASSERT_NE(it, map.end()) << "no successor for key " << k;
EXPECT_EQ(it->key(), keys[k + 1]) << "wrong successor for key " << k;
}
EXPECT_EQ(map.upperBound(keys.back()), map.end());
}
TEST_F(SHAMapTraversal, lower_bound_walks_the_whole_map_at_leaf_depth)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeysAtLeafDepth();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// lowerBound is the reverse direction: belowHelper descends to depth kLeafDepth to find the
// greatest key below a subtree.
for (std::size_t k = 1; k < keys.size(); ++k)
{
auto it = map.lowerBound(keys[k]);
ASSERT_NE(it, map.end()) << "no predecessor for key " << k;
EXPECT_EQ(it->key(), keys[k - 1]) << "wrong predecessor for key " << k;
}
EXPECT_EQ(map.lowerBound(keys.front()), map.end());
}
TEST_F(SHAMapTraversal, bounds_agree_with_iteration_for_absent_keys_at_leaf_depth)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeysAtLeafDepth();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// The keys fill all 16 branches of the last nibble, so an absent key must diverge from the
// shared 'a' prefix earlier than that. Diverging at increasingly deep nibbles forces
// walkTowardsKey to descend through more single-child inner nodes before it finds the empty
// branch, right up to the one just above kLeafDepth.
for (unsigned int const divergeAt : {0u, 31u, 61u, 62u})
{
// '9' sorts below the shared 'a' prefix and 'b' above it, so the probe lands under or
// over the whole key block -- driving belowHelper's First and Last descents respectively.
for (char const nibble : {'9', 'b'})
{
auto text = std::string(divergeAt, 'a') + nibble;
text.append(64 - text.size(), '0');
uint256 const probe{std::string_view{text}};
auto const expectedUpper = std::ranges::upper_bound(keys, probe);
auto const upper = map.upperBound(probe);
if (expectedUpper == keys.end())
{
EXPECT_EQ(upper, map.end()) << "divergeAt " << divergeAt << " nibble " << nibble;
}
else
{
ASSERT_NE(upper, map.end()) << "divergeAt " << divergeAt << " nibble " << nibble;
EXPECT_EQ(upper->key(), *expectedUpper)
<< "divergeAt " << divergeAt << " nibble " << nibble;
}
auto const lowerCount = std::ranges::lower_bound(keys, probe) - keys.begin();
auto const lower = map.lowerBound(probe);
if (lowerCount == 0)
{
EXPECT_EQ(lower, map.end()) << "divergeAt " << divergeAt << " nibble " << nibble;
}
else
{
ASSERT_NE(lower, map.end()) << "divergeAt " << divergeAt << " nibble " << nibble;
EXPECT_EQ(lower->key(), keys[lowerCount - 1])
<< "divergeAt " << divergeAt << " nibble " << nibble;
}
}
}
}
TEST_F(SHAMapTraversal, iteration_survives_deletions_at_leaf_depth)
{
tests::TestNodeFamily f{j_};
auto keys = deepFanOutKeysAtLeafDepth();
SHAMap map{SHAMapType::FREE, f};
fillMap(map, keys);
std::ranges::sort(keys);
// Deleting every other key drops the fan-out node's branch count from 16 to 8, the same
// non-collapsing case as iteration_survives_deletions above, but reached by descending through
// a chain of single-child inner nodes down to kLeafDepth instead of a shallow one.
for (std::size_t k = 0; k < keys.size(); k += 2)
{
ASSERT_TRUE(map.delItem(keys[k]));
map.invariants();
}
std::vector<uint256> expected;
for (std::size_t k = 1; k < keys.size(); k += 2)
expected.push_back(keys[k]);
std::vector<uint256> visited;
for (auto const& item : map)
visited.push_back(item.key());
EXPECT_EQ(visited, expected);
for (std::size_t k = 0; k + 1 < expected.size(); ++k)
{
auto it = map.upperBound(expected[k]);
ASSERT_NE(it, map.end());
EXPECT_EQ(it->key(), expected[k + 1]);
}
}
class SHAMapPathProof : public ::testing::Test
{
protected:

View File

@@ -9,6 +9,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/shamap/SHAMap.h>
#include <xrpl/shamap/SHAMapAddNode.h>
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <cstddef>

View File

@@ -93,9 +93,9 @@ class PerfLogImp : public PerfLog
// rpc and jq do not need mutex protection because all
// keys and values are created before more threads are started.
//
// Every key views the characters of a name in labels below, which the caller
// guarantees outlive this object, so the map copies no name to store one and
// needs no string to look one up.
// Every key views the characters of a name from the methodNames constructor
// parameter below, which the caller guarantees outlive this object, so the
// map copies no name to store one and needs no string to look one up.
std::unordered_map<std::string_view, Locked<Rpc>> rpc;
// The same names, in the order the caller gave them, and still carrying the
@@ -112,7 +112,7 @@ class PerfLogImp : public PerfLog
std::unordered_map<std::uint64_t, MethodStart> methods;
mutable std::mutex methodsMutex;
Counters(std::span<NullTerminatedView const> labels, JobTypes const& jobTypes);
Counters(std::span<NullTerminatedView const> methodNames, JobTypes const& jobTypes);
json::Value
countersJson() const;
json::Value

View File

@@ -623,8 +623,6 @@ ServerHandler::processSession(
}
else
{
if (jr[jss::result].isMember("forwarded") && jr[jss::result]["forwarded"])
jr = jr[jss::result];
jr[jss::status] = jss::success;
span.setOk();
}