xrpld
Loading...
Searching...
No Matches
SLEBase.cpp
1#include <xrpl/ledger/entries/SLEBase.h>
2
3#include <xrpl/beast/utility/Journal.h>
4#include <xrpl/ledger/ApplyView.h>
5#include <xrpl/ledger/ApplyViewImpl.h>
6#include <xrpl/ledger/OpenView.h>
7#include <xrpl/ledger/ReadView.h>
8#include <xrpl/ledger/entries/AMMEntry.h> // IWYU pragma: keep
9#include <xrpl/ledger/entries/AccountRootEntry.h>
10#include <xrpl/ledger/entries/AmendmentsEntry.h> // IWYU pragma: keep
11#include <xrpl/ledger/entries/BridgeEntry.h> // IWYU pragma: keep
12#include <xrpl/ledger/entries/CheckEntry.h> // IWYU pragma: keep
13#include <xrpl/ledger/entries/CredentialEntry.h> // IWYU pragma: keep
14#include <xrpl/ledger/entries/DIDEntry.h> // IWYU pragma: keep
15#include <xrpl/ledger/entries/DelegateEntry.h> // IWYU pragma: keep
16#include <xrpl/ledger/entries/DepositPreauthEntry.h> // IWYU pragma: keep
17#include <xrpl/ledger/entries/DirectoryNodeEntry.h> // IWYU pragma: keep
18#include <xrpl/ledger/entries/EscrowEntry.h> // IWYU pragma: keep
19#include <xrpl/ledger/entries/FeeSettingsEntry.h> // IWYU pragma: keep
20#include <xrpl/ledger/entries/LedgerHashesEntry.h> // IWYU pragma: keep
21#include <xrpl/ledger/entries/LoanBrokerEntry.h> // IWYU pragma: keep
22#include <xrpl/ledger/entries/LoanEntry.h> // IWYU pragma: keep
23#include <xrpl/ledger/entries/MPTokenEntry.h> // IWYU pragma: keep
24#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h> // IWYU pragma: keep
25#include <xrpl/ledger/entries/NFTokenOfferEntry.h> // IWYU pragma: keep
26#include <xrpl/ledger/entries/NFTokenPageEntry.h> // IWYU pragma: keep
27#include <xrpl/ledger/entries/NegativeUNLEntry.h> // IWYU pragma: keep
28#include <xrpl/ledger/entries/OfferEntry.h>
29#include <xrpl/ledger/entries/OracleEntry.h> // IWYU pragma: keep
30#include <xrpl/ledger/entries/PayChannelEntry.h> // IWYU pragma: keep
31#include <xrpl/ledger/entries/PermissionedDomainEntry.h> // IWYU pragma: keep
32#include <xrpl/ledger/entries/RippleStateEntry.h> // IWYU pragma: keep
33#include <xrpl/ledger/entries/SignerListEntry.h> // IWYU pragma: keep
34#include <xrpl/ledger/entries/SponsorshipEntry.h> // IWYU pragma: keep
35#include <xrpl/ledger/entries/TicketEntry.h>
36#include <xrpl/ledger/entries/TransactionProposalEntry.h> // IWYU pragma: keep
37#include <xrpl/ledger/entries/VaultEntry.h> // IWYU pragma: keep
38#include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h> // IWYU pragma: keep
39#include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h> // IWYU pragma: keep
40#include <xrpl/protocol/Indexes.h>
41#include <xrpl/protocol/Keylet.h>
42#include <xrpl/protocol/LedgerFormats.h>
43#include <xrpl/protocol/SField.h>
44#include <xrpl/protocol/STLedgerEntry.h>
45#include <xrpl/protocol/SeqProxy.h>
46#include <xrpl/protocol/XRPAmount.h>
47#include <xrpl/protocol_autogen/transactions/AccountSet.h>
48
49#include <gtest/gtest.h>
50#include <helpers/Account.h>
51#include <helpers/TxTest.h>
52
53#include <stdexcept>
54#include <tuple>
55#include <type_traits>
56
57namespace xrpl {
58
59// An un-instantiated class template is barely type-checked. Name every entry
60// class in a static_assert below so its class definition is instantiated and
61// type-checked; the per-type suites check the member bodies. Keep this block
62// even once real call sites exist: it is what catches a new ledger entry type
63// being added without its wrapper class.
64//
65// Driving this off ledger_entries.macro keeps it exhaustive by construction:
66// adding a ledger entry type without adding its entry class stops compiling
67// here, and the static_assert pins each one to the right LedgerEntryType.
68//
69// Keep this loop in one file rather than splitting it across the per-entry
70// *Entry_test.cpp suites. Those are hand-written, so a new ledger entry type
71// would simply have no file there and nothing would complain; this is the only
72// thing making the coverage exhaustive rather than merely extensive.
73
74template class SLEBase<ReadView>;
75template class SLEBase<ApplyView>;
76
77#pragma push_macro("LEDGER_ENTRY")
78#undef LEDGER_ENTRY
79
80// No explicit instantiation here, neither a definition nor a declaration.
81// A definition would instantiate an entry a second time once its member
82// bodies live in its own .cpp (MSVC rejects that with C4661 under /WX), and a
83// declaration (`extern template`) stops clang from emitting the inline
84// constructors of the header-only entries at -O0, which leaves them undefined
85// at link time in the debug builds. Naming the class in a static_assert is
86// enough to instantiate its definition, which is all this exhaustiveness
87// check needs; the per-type suites exercise the members.
88#define LEDGER_ENTRY(tag, value, name, ...) \
89 static_assert( \
90 name##Entry<ReadView>::kEntryType == tag && name##Entry<ApplyView>::kEntryType == tag, \
91 #name "Entry must be bound to " #tag); \
92 static_assert( \
93 std::is_constructible_v<name##Entry<ReadView>, Keylet const&, ReadView const&> && \
94 std::is_constructible_v<name##Entry<ApplyView>, Keylet const&, ApplyView&>, \
95 #name "Entry must be constructible from a Keylet and a view");
96
97#include <xrpl/protocol/detail/ledger_entries.macro>
98
99#undef LEDGER_ENTRY
100#pragma pop_macro("LEDGER_ENTRY")
101
102// --- Entry-type safety, checked at compile time. ---
103//
104// The writable -> read-only converting constructor is inherited into every
105// per-type entry, so without the entry-type constraint it will bind any
106// writable entry that slices to SLEBase. These assertions pin down which
107// conversions are legal.
108
109// An entry class for one entry type must never be constructible from another.
110static_assert(
112 "cross-entry-type conversion must not compile");
113static_assert(
115 "cross-entry-type construction must not compile, even explicitly");
116static_assert(
118 "read-only cross-entry-type conversion must not compile");
119
120// Nor from a type-erased writable entry, which carries no static type.
121static_assert(
123 "generic -> typed conversion must not compile");
124
125// The intended conversions must keep working: same type writable -> read-only,
126// and typed -> generic widening.
127static_assert(
129 "same-type writable -> read-only conversion must keep working");
130static_assert(
132 "typed -> generic widening must keep working");
133
134// Detection idioms for the writable interface. These have to go through a
135// template parameter: a requires-expression over a concrete type is checked
136// eagerly, so spelling the calls out inline would be a hard error rather than
137// the `false` the assertions below want.
138template <typename T>
139concept HasMutableRawSle = requires(T& t) { t.mutableRawSle(); };
140
141template <typename T>
142concept HasApplyView = requires(T& t) { t.applyView(); };
143
144namespace test {
145
151class SLEBaseTests : public ::testing::Test
152{
153protected:
155 Account const alice_{"alice"};
156 Account const bob_{"bob"};
157
159 {
160 env_.createAccount(alice_, XRP(10'000));
161 }
162};
163
165{
166 AccountRootEntryR const absent(bob_.id(), env_.getClosedLedger());
167 EXPECT_FALSE(absent.exists());
168 EXPECT_FALSE(static_cast<bool>(absent));
169 // A typed entry knows its entry type even with nothing to read.
170 EXPECT_EQ(absent.type(), ltACCOUNT_ROOT);
171
172 AccountRootEntryR const present(alice_.id(), env_.getClosedLedger());
173 EXPECT_TRUE(present.exists());
174 EXPECT_TRUE(static_cast<bool>(present));
175 EXPECT_EQ(present.key(), keylet::account(alice_.id()).key);
176 EXPECT_EQ(present.type(), ltACCOUNT_ROOT);
177 EXPECT_EQ(present.keylet().type, ltACCOUNT_ROOT);
178 EXPECT_EQ(present->getType(), ltACCOUNT_ROOT);
179 EXPECT_EQ((*present).getType(), ltACCOUNT_ROOT);
180 EXPECT_EQ(&present.readView(), &env_.getClosedLedger());
181}
182
184{
185 auto const sle = env_.getClosedLedger().read(keylet::account(alice_.id()));
186 ASSERT_NE(sle, nullptr);
187
188 AccountRootEntryR const adopted(sle, env_.getClosedLedger());
189 EXPECT_TRUE(adopted.exists());
190 EXPECT_EQ(adopted.rawSle(), sle);
191 EXPECT_EQ(adopted.key(), keylet::account(alice_.id()).key);
192 EXPECT_EQ(adopted.type(), ltACCOUNT_ROOT);
193 // keylet() reports the SLE's own type, not the entry's static binding, so
194 // it stays truthful in a Release build where the constructor's
195 // entry-type assert is compiled out.
196 EXPECT_EQ(adopted.keylet().type, ltACCOUNT_ROOT);
197
198 // Adopting a null SLE is allowed: the assert only fires on a
199 // type mismatch, and a null pointer has no type to mismatch.
200 AccountRootEntryR const empty(SLE::const_pointer{}, env_.getClosedLedger());
201 EXPECT_FALSE(empty.exists());
202 EXPECT_EQ(empty.type(), ltACCOUNT_ROOT);
203
204 // A generic entry adopting the same SLE has to read the type back.
205 ReadOnlySLE const generic(sle, env_.getClosedLedger());
206 EXPECT_TRUE(generic.exists());
207 EXPECT_EQ(generic.type(), ltACCOUNT_ROOT);
208 EXPECT_EQ(generic.keylet().type, ltACCOUNT_ROOT);
209
210 // There is deliberately no writable equivalent.
211 static_assert(
213 "writable entries must not be constructible from a bare SLE");
214}
215
216TEST_F(SLEBaseTests, writable_accessors)
217{
218 ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
220
221 AccountRootEntryW account(alice_.id(), av, j);
222 EXPECT_TRUE(account.exists());
223 EXPECT_EQ(account.mutableRawSle(), account.rawSle());
224 EXPECT_EQ(&account.applyView(), &av);
225 EXPECT_EQ(&account.readView(), static_cast<ReadView const*>(&av));
226 EXPECT_EQ(&account.journal().sink(), &j.sink());
227
228 // The mutable dereference operators reach the same entry.
229 EXPECT_EQ(account.operator->(), account.rawSle().get());
230 EXPECT_EQ(&*account, account.rawSle().get());
231
232 // Everything handing out mutable access is non-const, so a const
233 // writable entry is as inert as a read-only one.
235 static_assert(HasApplyView<AccountRootEntryW>);
236 static_assert(
238 "mutableRawSle() must not be callable on a const writable entry");
239 static_assert(
241 "applyView() must not be callable on a const writable entry");
242
243 // Read-only entries do not have the writable interface at all.
244 static_assert(
246 "mutableRawSle() must not exist on a read-only entry");
247 static_assert(
248 !HasApplyView<AccountRootEntryR>, "applyView() must not exist on a read-only entry");
249}
250
251TEST_F(SLEBaseTests, apply_view_context_ctor)
252{
253 ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
255
256 transactions::AccountSetBuilder builder{alice_.id()};
257 builder.setSequence(env_.getAccountRoot(alice_.id()).getSequence());
258 builder.setFee(XRPAmount(10));
259 auto const tx = builder.build(alice_.pk(), alice_.sk()).getSTTx();
260 ASSERT_NE(tx, nullptr);
261 ApplyViewContext const ctx{.view = av, .tx = *tx};
262
263 // Delegates to the (Keylet, ApplyView&) constructor; ctx.tx is not
264 // retained, so this must be indistinguishable from building from
265 // ctx.view directly.
266 AccountRootEntryW fromCtx(keylet::account(alice_.id()), ctx, j);
267 EXPECT_TRUE(fromCtx.exists());
268 EXPECT_EQ(&fromCtx.applyView(), &av);
269 EXPECT_EQ(fromCtx.key(), keylet::account(alice_.id()).key);
270
271 AccountRootEntryW const fromView(keylet::account(alice_.id()), av, j);
272 EXPECT_EQ(fromCtx.rawSle(), fromView.rawSle());
273}
274
275TEST_F(SLEBaseTests, writable_lifecycle)
276{
277 // A view we never apply, so nothing here reaches the ledger.
278 ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
279
280 // Entry that does not exist yet: newSLE() -> insert().
281 {
283 EXPECT_FALSE(ticket.exists());
284 EXPECT_EQ(ticket.key(), keylet::ticket(alice_.id(), SeqProxy::rawTicket(1)).key);
285 EXPECT_EQ(ticket.type(), ltTICKET);
286 EXPECT_EQ(ticket.keylet().type, ltTICKET);
287
288 ticket.newSLE();
289 EXPECT_TRUE(ticket.exists());
290 ticket.insert();
291 ticket.update();
292
293 // Erasing an entry inserted in this same view drops it outright.
294 ticket.erase();
295 EXPECT_FALSE(ticket.exists());
296 }
297
298 // Entry that already exists: update() is what promotes it from a bare
299 // peek to a real change. ApplyViewImpl::size() counts Insert, Modify and
300 // Erase but not Cache, so it shows the difference: building the entry
301 // only peeks, and the write is invisible to the view until update().
302 {
303 ApplyViewImpl fresh(&env_.getClosedLedger(), TapNone);
304
305 AccountRootEntryW account(alice_.id(), fresh);
306 EXPECT_TRUE(account.exists());
307 EXPECT_EQ(fresh.size(), 0);
308
309 account->setFieldU32(sfSequence, account->getFieldU32(sfSequence) + 1);
310 EXPECT_EQ(fresh.size(), 0);
311
312 account.update();
313 EXPECT_EQ(fresh.size(), 1);
314
315 // update() is idempotent: the entry is already a Modify.
316 account.update();
317 EXPECT_EQ(fresh.size(), 1);
318 }
319
320 // Entry that already exists. ApplyStateTable::erase() keeps holding
321 // this exact SLE and builds the DeletedNode's FinalFields from it, so
322 // the entry must drop its pointer or a later write would silently
323 // land in transaction metadata.
324 {
325 AccountRootEntryW account(alice_.id(), av);
326 EXPECT_TRUE(account.exists());
327
328 account.erase();
329 EXPECT_FALSE(account.exists());
330 }
331}
332
334{
335 ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
336
337 AccountRootEntryW const writable(alice_.id(), av);
338 EXPECT_TRUE(writable.exists());
339
340 AccountRootEntryR const readOnly = writable;
341 EXPECT_TRUE(readOnly.exists());
342 EXPECT_EQ(readOnly.rawSle(), writable.rawSle());
343
344 ReadOnlySLE const generic = writable;
345 EXPECT_TRUE(generic.exists());
346 EXPECT_EQ(generic.rawSle(), writable.rawSle());
347 // A generic entry has to read the type back out of the SLE.
348 EXPECT_EQ(generic.type(), ltACCOUNT_ROOT);
349}
350
351TEST_F(SLEBaseTests, resolve_entry_peeks)
352{
353 // getOpenLedger() is an OpenView, which derives from ReadView but not
354 // from ApplyView, so resolveEntry's dynamic_cast fails and this takes
355 // the plain ReadView::read() path.
356 OpenView const& ledger = env_.getOpenLedger();
357 AccountRootEntryR const overLedger(alice_.id(), ledger);
358 EXPECT_TRUE(overLedger.exists());
359
360 ApplyViewImpl av(&ledger, TapNone);
361
362 // ReadView const& binds an ApplyViewImpl just as happily, and there the
363 // dynamic_cast succeeds, so this one resolves through ApplyView::peek().
364 AccountRootEntryR const readOnly(alice_.id(), av);
365 EXPECT_TRUE(readOnly.exists());
366
367 AccountRootEntryW writable(alice_.id(), av);
368 EXPECT_TRUE(writable.exists());
369
370 // The invariant resolveEntry() exists to hold: one SLE per key per
371 // view. read() would have handed back the base ledger's entry instead,
372 // which is a different object.
373 EXPECT_EQ(readOnly.rawSle(), writable.rawSle());
374 EXPECT_NE(readOnly.rawSle(), overLedger.rawSle());
375
376 // Which is what keeps a read-only entry from going stale: a write
377 // through any other entry over the same view is visible through it.
378 auto const bumped = writable->getFieldU32(sfSequence) + 1;
379 writable->setFieldU32(sfSequence, bumped);
380 EXPECT_EQ(readOnly->getFieldU32(sfSequence), bumped);
381}
382
383TEST_F(SLEBaseTests, throws_on_missing_entry)
384{
385 // A generic read-only entry has no static type to fall back on, so
386 // type() must read it off the (absent) SLE and throw.
387 ReadOnlySLE const absent(keylet::account(bob_.id()), env_.getClosedLedger());
388 EXPECT_FALSE(absent.exists());
389 EXPECT_THROW(std::ignore = absent.type(), std::logic_error);
390
391 // A per-type read-only entry always knows its type, but keylet() and
392 // key() still have to derive the ledger key from the SLE.
393 AccountRootEntryR const missing(bob_.id(), env_.getClosedLedger());
394 EXPECT_FALSE(missing.exists());
395 EXPECT_THROW(std::ignore = missing.key(), std::logic_error);
396 EXPECT_THROW(std::ignore = missing.keylet(), std::logic_error);
397
398 // Dereferencing an absent entry throws rather than handing back a null
399 // pointer for the caller to walk into.
400 EXPECT_THROW(std::ignore = missing.operator->(), std::logic_error);
401 EXPECT_THROW(std::ignore = (*missing).getType(), std::logic_error);
402}
403
404TEST_F(SLEBaseTests, throws_on_missing_writable_entry)
405{
406 // A view we never apply, so nothing here reaches the ledger.
407 ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
408
409 // bob is unfunded, so this resolves to nothing and every operation that
410 // needs an SLE has to throw instead of dereferencing null. These are the
411 // cases a Release build used to walk straight past, back when they were
412 // XRPL_ASSERTs.
413 AccountRootEntryW missing(bob_.id(), av);
414 EXPECT_FALSE(missing.exists());
415
416 EXPECT_THROW(std::ignore = missing.operator->(), std::logic_error);
417 EXPECT_THROW(std::ignore = (*missing).getType(), std::logic_error);
418 EXPECT_THROW(missing.insert(), std::logic_error);
419 EXPECT_THROW(missing.update(), std::logic_error);
420 EXPECT_THROW(missing.erase(), std::logic_error);
421
422 // keylet() and key() are the exception: a writable entry keeps the keylet
423 // it was built from, so they stay valid before newSLE().
424 EXPECT_EQ(missing.key(), keylet::account(bob_.id()).key);
425
426 // newSLE() is the inverse -- it throws when the entry *does* exist,
427 // rather than silently dropping the SLE already held.
428 missing.newSLE();
429 EXPECT_TRUE(missing.exists());
430 EXPECT_THROW(missing.newSLE(), std::logic_error);
431
432 // And once erased, the entry is empty again and throws as before.
433 missing.insert();
434 missing.erase();
435 EXPECT_FALSE(missing.exists());
436 EXPECT_THROW(missing.update(), std::logic_error);
437}
438
439} // namespace test
440} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Sink & sink() const
Returns the Sink associated with this Journal.
Definition Journal.h:308
static Sink & getNullSink()
Returns a Sink which does nothing.
Editable, discardable view that can build metadata for one tx.
std::size_t size()
Get the number of modified entries.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:59
A view into a ledger.
Definition ReadView.h:41
View-parameterized base class for all ledger entries.
Definition SLEBase.h:92
LedgerEntryType type() const
Definition SLEBase.h:264
ReadView const & readView() const
Definition SLEBase.h:322
SLE::const_pointer rawSle() const
Definition SLEBase.h:248
LedgerEntryType getType() const
std::shared_ptr< STLedgerEntry const > const_pointer
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:601
void setFieldU32(SField const &field, std::uint32_t)
Definition STObject.cpp:743
static constexpr SeqProxy rawTicket(std::uint32_t v)
Factory function to return a ticket-based SeqProxy.
Definition SeqProxy.h:74
Scaffolding shared by the test cases below: a funded alice, an unfunded bob (for the entries that nee...
Definition SLEBase.cpp:152
A lightweight transaction testing harness.
Definition TxTest.h:185
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
AccountSet build(PublicKey const &publicKey, SecretKey const &secretKey)
Build and return the AccountSet wrapper.
Derived & setFee(STAmount const &value)
Set the transaction fee.
Derived & setSequence(std::uint32_t const &value)
Set the sequence number.
T is_constructible_v
T is_convertible_v
Keylet ticket(AccountID const &id, SeqProxy const &ticketSeq)
A ticket belonging to an account.
Definition Indexes.cpp:332
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Ticket operations.
Definition ticket.cpp:13
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
AccountRootEntry< ReadView > AccountRootEntryR
TEST_F(HardenedHashTest, user_types)
TicketEntry< ApplyView > TicketEntryW
Definition TicketEntry.h:44
@ TapNone
Definition ApplyView.h:28
AccountRootEntry< ApplyView > AccountRootEntryW
SLEBase< ReadView > ReadOnlySLE
Generic (any-entry-type) SLE entries.
Definition SLEBase.h:496
Bundles the mutable ledger view and the transaction being applied.
Definition ApplyView.h:451
LedgerEntryType type
Definition Keylet.h:22