Files
rippled/src/libxrpl/protocol/Indexes.cpp
2026-05-15 15:32:19 +00:00

583 lines
16 KiB
C++

#include <xrpl/protocol/Indexes.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/nftPageMask.h>
#include <boost/endian/conversion.hpp>
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <set>
#include <utility>
#include <variant>
#include <vector>
namespace xrpl {
/** Type-specific prefix for calculating ledger indices.
The identifier for a given object within the ledger is calculated based
on some object-specific parameters. To ensure that different types of
objects have different indices, even if they happen to use the same set
of parameters, we use "tagged hashing" by adding a type-specific prefix.
@note These values are part of the protocol and *CANNOT* be arbitrarily
changed. If they were, on-ledger objects may no longer be able to
be located or addressed.
Additions to this list are OK, but changing existing entries to
assign them a different values should never be needed.
Entries that are removed should be moved to the bottom of the enum
and marked as [[deprecated]] to prevent accidental reuse.
*/
enum class LedgerNameSpace : std::uint16_t {
Account = 'a',
DirNode = 'd',
TrustLine = 'r',
Offer = 'o',
OwnerDir = 'O',
BookDir = 'B',
SkipList = 's',
Escrow = 'u',
Amendments = 'f',
FeeSettings = 'e',
Ticket = 'T',
SignerList = 'S',
XRPPaymentChannel = 'x',
Check = 'C',
DepositPreauth = 'p',
DepositPreauthCredentials = 'P',
NegativeUnl = 'N',
NftokenOffer = 'q',
NftokenBuyOffers = 'h',
NftokenSellOffers = 'i',
Amm = 'A',
Bridge = 'H',
XchainClaimId = 'Q',
XchainCreateAccountClaimId = 'K',
Did = 'I',
Oracle = 'R',
MPTokenIssuance = '~',
MPToken = 't',
Credential = 'D',
PermissionedDomain = 'm',
Delegate = 'E',
Vault = 'V',
LoanBroker = 'l', // lower-case L
Loan = 'L',
// No longer used or supported. Left here to reserve the space to avoid accidental reuse.
Contract [[deprecated]] = 'c',
Generator [[deprecated]] = 'g',
Nickname [[deprecated]] = 'n',
};
template <class... Args>
static uint256
indexHash(LedgerNameSpace space, Args const&... args)
{
return sha512Half(safeCast<std::uint16_t>(space), args...);
}
uint256
getBookBase(Book const& book)
{
XRPL_ASSERT(isConsistent(book), "xrpl::getBookBase : input is consistent");
auto getIndexHash = [&book]<typename... Args>(Args... args) {
if (book.domain)
return indexHash(std::forward<Args>(args)..., *book.domain);
return indexHash(std::forward<Args>(args)...);
};
auto const index = std::visit(
[&]<ValidIssueType TIn, ValidIssueType TOut>(TIn const& in, TOut const& out) {
if constexpr (std::is_same_v<TIn, Issue> && std::is_same_v<TOut, Issue>)
{
return getIndexHash(
LedgerNameSpace::BookDir, in.currency, out.currency, in.account, out.account);
}
else if constexpr (std::is_same_v<TIn, Issue> && std::is_same_v<TOut, MPTIssue>)
{
return getIndexHash(
LedgerNameSpace::BookDir, in.currency, out.getMptID(), in.account);
}
else if constexpr (std::is_same_v<TIn, MPTIssue> && std::is_same_v<TOut, Issue>)
{
return getIndexHash(
LedgerNameSpace::BookDir, in.getMptID(), out.currency, out.account);
}
else
{
return getIndexHash(LedgerNameSpace::BookDir, in.getMptID(), out.getMptID());
}
},
book.in.value(),
book.out.value());
// Return with quality 0.
auto k = keylet::quality({ltDIR_NODE, index}, 0);
return k.key;
}
uint256
getQualityNext(uint256 const& uBase)
{
static constexpr uint256 kNextQuality(
"0000000000000000000000000000000000000000000000010000000000000000");
return uBase + kNextQuality;
}
std::uint64_t
getQuality(uint256 const& uBase)
{
// VFALCO [base_uint] This assumes a certain storage format
return boost::endian::big_to_native(((std::uint64_t*)uBase.end())[-1]);
}
uint256
getTicketIndex(AccountID const& account, std::uint32_t ticketSeq)
{
return indexHash(LedgerNameSpace::Ticket, account, ticketSeq);
}
uint256
getTicketIndex(AccountID const& account, SeqProxy ticketSeq)
{
XRPL_ASSERT(ticketSeq.isTicket(), "xrpl::getTicketIndex : valid input");
return getTicketIndex(account, ticketSeq.value());
}
MPTID
makeMptID(std::uint32_t sequence, AccountID const& account)
{
MPTID u;
sequence = boost::endian::native_to_big(sequence);
memcpy(u.data(), &sequence, sizeof(sequence));
memcpy(u.data() + sizeof(sequence), account.data(), sizeof(account));
return u;
}
//------------------------------------------------------------------------------
namespace keylet {
Keylet
account(AccountID const& id) noexcept
{
return Keylet{ltACCOUNT_ROOT, indexHash(LedgerNameSpace::Account, id)};
}
Keylet
child(uint256 const& key) noexcept
{
return {ltCHILD, key};
}
Keylet const&
skip() noexcept
{
static Keylet const kRet{ltLEDGER_HASHES, indexHash(LedgerNameSpace::SkipList)};
return kRet;
}
Keylet
skip(LedgerIndex ledger) noexcept
{
return {
ltLEDGER_HASHES,
indexHash(
LedgerNameSpace::SkipList, std::uint32_t(static_cast<std::uint32_t>(ledger) >> 16))};
}
Keylet const&
amendments() noexcept
{
static Keylet const kRet{ltAMENDMENTS, indexHash(LedgerNameSpace::Amendments)};
return kRet;
}
Keylet const&
fees() noexcept
{
static Keylet const kRet{ltFEE_SETTINGS, indexHash(LedgerNameSpace::FeeSettings)};
return kRet;
}
Keylet const&
negativeUNL() noexcept
{
static Keylet const kRet{ltNEGATIVE_UNL, indexHash(LedgerNameSpace::NegativeUnl)};
return kRet;
}
Keylet
BookT::operator()(Book const& b) const
{
return {ltDIR_NODE, getBookBase(b)};
}
Keylet
line(AccountID const& id0, AccountID const& id1, Currency const& currency) noexcept
{
// There is code in TrustSet that calls us with id0 == id1, to allow users
// to locate and delete such "weird" trustlines. If we remove that code, we
// could enable this assert:
// XRPL_ASSERT(id0 != id1, "xrpl::keylet::line : accounts must be
// different");
// A trust line is shared between two accounts; while we typically think
// of this as an "issuer" and a "holder" the relationship is actually fully
// bidirectional.
//
// So that we can generate a unique ID for a trust line, regardess of which
// side of the line we're looking at, we define a "canonical" order for the
// two accounts (smallest then largest) and hash them in that order:
auto const accounts = std::minmax(id0, id1);
return {
ltRIPPLE_STATE,
indexHash(LedgerNameSpace::TrustLine, accounts.first, accounts.second, currency)};
}
Keylet
offer(AccountID const& id, std::uint32_t seq) noexcept
{
return {ltOFFER, indexHash(LedgerNameSpace::Offer, id, seq)};
}
Keylet
quality(Keylet const& k, std::uint64_t q) noexcept
{
XRPL_ASSERT(k.type == ltDIR_NODE, "xrpl::keylet::quality : valid input type");
// Indexes are stored in big endian format: they print as hex as stored.
// Most significant bytes are first and the least significant bytes
// represent adjacent entries. We place the quality, in big endian format,
// in the 8 right most bytes; this way, incrementing goes to the next entry
// for indexes.
uint256 x = k.key;
// FIXME This is ugly and we can and should do better...
((std::uint64_t*)x.end())[-1] = boost::endian::native_to_big(q);
return {ltDIR_NODE, x};
}
Keylet
NextT::operator()(Keylet const& k) const
{
XRPL_ASSERT(k.type == ltDIR_NODE, "xrpl::keylet::NextT::operator() : valid input type");
return {ltDIR_NODE, getQualityNext(k.key)};
}
Keylet
TicketT::operator()(AccountID const& id, std::uint32_t ticketSeq) const
{
return {ltTICKET, getTicketIndex(id, ticketSeq)};
}
Keylet
TicketT::operator()(AccountID const& id, SeqProxy ticketSeq) const
{
return {ltTICKET, getTicketIndex(id, ticketSeq)};
}
// This function is presently static, since it's never accessed from anywhere
// else. If we ever support multiple pages of signer lists, this would be the
// keylet used to locate them.
static Keylet
signers(AccountID const& account, std::uint32_t page) noexcept
{
return {ltSIGNER_LIST, indexHash(LedgerNameSpace::SignerList, account, page)};
}
Keylet
signers(AccountID const& account) noexcept
{
return signers(account, 0);
}
Keylet
check(AccountID const& id, std::uint32_t seq) noexcept
{
return {ltCHECK, indexHash(LedgerNameSpace::Check, id, seq)};
}
Keylet
depositPreauth(AccountID const& owner, AccountID const& preauthorized) noexcept
{
return {ltDEPOSIT_PREAUTH, indexHash(LedgerNameSpace::DepositPreauth, owner, preauthorized)};
}
// Credentials should be sorted here, use credentials::makeSorted
Keylet
depositPreauth(
AccountID const& owner,
std::set<std::pair<AccountID, Slice>> const& authCreds) noexcept
{
std::vector<uint256> hashes;
hashes.reserve(authCreds.size());
for (auto const& o : authCreds)
hashes.emplace_back(sha512Half(o.first, o.second));
return {
ltDEPOSIT_PREAUTH, indexHash(LedgerNameSpace::DepositPreauthCredentials, owner, hashes)};
}
//------------------------------------------------------------------------------
Keylet
unchecked(uint256 const& key) noexcept
{
return {ltANY, key};
}
Keylet
ownerDir(AccountID const& id) noexcept
{
return {ltDIR_NODE, indexHash(LedgerNameSpace::OwnerDir, id)};
}
Keylet
page(uint256 const& key, std::uint64_t index) noexcept
{
if (index == 0)
return {ltDIR_NODE, key};
return {ltDIR_NODE, indexHash(LedgerNameSpace::DirNode, key, index)};
}
Keylet
escrow(AccountID const& src, std::uint32_t seq) noexcept
{
return {ltESCROW, indexHash(LedgerNameSpace::Escrow, src, seq)};
}
Keylet
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept
{
return {ltPAYCHAN, indexHash(LedgerNameSpace::XRPPaymentChannel, src, dst, seq)};
}
Keylet
nftpageMin(AccountID const& owner)
{
std::array<std::uint8_t, 32> buf{};
std::memcpy(buf.data(), owner.data(), owner.size());
return {ltNFTOKEN_PAGE, uint256::fromRaw(buf)};
}
Keylet
nftpageMax(AccountID const& owner)
{
uint256 id = nft::kPageMask;
std::memcpy(id.data(), owner.data(), owner.size());
return {ltNFTOKEN_PAGE, id};
}
Keylet
nftpage(Keylet const& k, uint256 const& token)
{
XRPL_ASSERT(k.type == ltNFTOKEN_PAGE, "xrpl::keylet::nftpage : valid input type");
return {ltNFTOKEN_PAGE, (k.key & ~nft::kPageMask) + (token & nft::kPageMask)};
}
Keylet
nftoffer(AccountID const& owner, std::uint32_t seq)
{
return {ltNFTOKEN_OFFER, indexHash(LedgerNameSpace::NftokenOffer, owner, seq)};
}
Keylet
nftBuys(uint256 const& id) noexcept
{
return {ltDIR_NODE, indexHash(LedgerNameSpace::NftokenBuyOffers, id)};
}
Keylet
nftSells(uint256 const& id) noexcept
{
return {ltDIR_NODE, indexHash(LedgerNameSpace::NftokenSellOffers, id)};
}
Keylet
amm(Asset const& asset1, Asset const& asset2) noexcept
{
auto const& [minA, maxA] = std::minmax(asset1, asset2);
return std::visit(
[]<ValidIssueType TIss1, ValidIssueType TIss2>(TIss1 const& issue1, TIss2 const& issue2) {
if constexpr (std::is_same_v<TIss1, Issue> && std::is_same_v<TIss2, Issue>)
{
return amm(indexHash(
LedgerNameSpace::Amm,
issue1.account,
issue1.currency,
issue2.account,
issue2.currency));
}
else if constexpr (std::is_same_v<TIss1, Issue> && std::is_same_v<TIss2, MPTIssue>)
{
return amm(indexHash(
LedgerNameSpace::Amm, issue1.account, issue1.currency, issue2.getMptID()));
}
else if constexpr (std::is_same_v<TIss1, MPTIssue> && std::is_same_v<TIss2, Issue>)
{
return amm(indexHash(
LedgerNameSpace::Amm, issue1.getMptID(), issue2.account, issue2.currency));
}
else if constexpr (std::is_same_v<TIss1, MPTIssue> && std::is_same_v<TIss2, MPTIssue>)
{
return amm(indexHash(LedgerNameSpace::Amm, issue1.getMptID(), issue2.getMptID()));
}
},
minA.value(),
maxA.value());
}
Keylet
amm(uint256 const& id) noexcept
{
return {ltAMM, id};
}
Keylet
delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept
{
return {ltDELEGATE, indexHash(LedgerNameSpace::Delegate, account, authorizedAccount)};
}
Keylet
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType)
{
// A door account can support multiple bridges. On the locking chain
// there can only be one bridge per lockingChainCurrency. On the issuing
// chain there can only be one bridge per issuingChainCurrency.
auto const& issue = bridge.issue(chainType);
return {ltBRIDGE, indexHash(LedgerNameSpace::Bridge, bridge.door(chainType), issue.currency)};
}
Keylet
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq)
{
return {
ltXCHAIN_OWNED_CLAIM_ID,
indexHash(
LedgerNameSpace::XchainClaimId,
bridge.lockingChainDoor(),
bridge.lockingChainIssue(),
bridge.issuingChainDoor(),
bridge.issuingChainIssue(),
seq)};
}
Keylet
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq)
{
return {
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
indexHash(
LedgerNameSpace::XchainCreateAccountClaimId,
bridge.lockingChainDoor(),
bridge.lockingChainIssue(),
bridge.issuingChainDoor(),
bridge.issuingChainIssue(),
seq)};
}
Keylet
did(AccountID const& account) noexcept
{
return {ltDID, indexHash(LedgerNameSpace::Did, account)};
}
Keylet
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
{
return {ltORACLE, indexHash(LedgerNameSpace::Oracle, account, documentID)};
}
Keylet
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept
{
return mptIssuance(makeMptID(seq, issuer));
}
Keylet
mptIssuance(MPTID const& issuanceID) noexcept
{
return {ltMPTOKEN_ISSUANCE, indexHash(LedgerNameSpace::MPTokenIssuance, issuanceID)};
}
Keylet
mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept
{
return mptoken(mptIssuance(issuanceID).key, holder);
}
Keylet
mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept
{
return {ltMPTOKEN, indexHash(LedgerNameSpace::MPToken, issuanceKey, holder)};
}
Keylet
credential(AccountID const& subject, AccountID const& issuer, Slice const& credType) noexcept
{
return {ltCREDENTIAL, indexHash(LedgerNameSpace::Credential, subject, issuer, credType)};
}
Keylet
vault(AccountID const& owner, std::uint32_t seq) noexcept
{
return vault(indexHash(LedgerNameSpace::Vault, owner, seq));
}
Keylet
loanbroker(AccountID const& owner, std::uint32_t seq) noexcept
{
return loanbroker(indexHash(LedgerNameSpace::LoanBroker, owner, seq));
}
Keylet
loan(uint256 const& loanBrokerID, std::uint32_t loanSeq) noexcept
{
return loan(indexHash(LedgerNameSpace::Loan, loanBrokerID, loanSeq));
}
Keylet
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept
{
return {ltPERMISSIONED_DOMAIN, indexHash(LedgerNameSpace::PermissionedDomain, account, seq)};
}
Keylet
permissionedDomain(uint256 const& domainID) noexcept
{
return {ltPERMISSIONED_DOMAIN, domainID};
}
} // namespace keylet
} // namespace xrpl