advanced 150 min

Serialized Ledger Entries

How the XRP Ledger represents its state: Serialized Ledger Entries (SLEs), the structured objects that encode accounts, offers, trust lines, escrows, and other persistent ledger data. Learn how SLEs are defined, identified, serialized, accessed, and modified inside `rippled`, and how they connect transactions and protocol logic to the ledger’s underlying storage.

What you'll learn

  • * Explain what a Serialized Ledger Entry (SLE) is and its role in representing the current state of the XRP Ledger.
  • * Understand the structure of an SLE: ledger entry types, fields, flags, and serialized values.
  • * Explain how SLEs are uniquely identified using ledger keys and how those keys are derived.
  • * Navigate the `rippled` codebase to see how SLE types and fields are defined and accessed.
  • * Understand how transaction processing reads, creates, modifies, and deletes ledger entries through ledger views.
  • * Connect SLEs to the underlying SHAMap and NodeStore layers that ultimately persist ledger state.
  • * Trace the lifecycle of a ledger object from transaction execution to its serialized representation in the ledger.
Complete this module by mentor review. Jump to assessment

Serialized Ledger Entries

Introduction

≈90 min including exercises · Advanced · builds on State-management foundations and SHAMap

The SHAMap module explained how a tree commits to ledger state. Now let's open one of its state leaves: what do the bytes inside actually represent?

They represent a ledger entry: an account, a trust line, an offer, an escrow, or another piece of persistent protocol state. In rippled, the central C++ representation is STLedgerEntry, usually called SLE.

This module follows one AccountRoot from its field definitions to its binary representation and then into the state SHAMap. By the end, you should be able to:

  • Distinguish an entry's key, contents, and leaf hash.
  • Find the schema and serialized field definitions for an entry type.
  • Explain how canonical serialization produces deterministic bytes.
  • Read and modify entries through rippled's ledger-view interfaces.
  • Trace a state change into both the state tree and transaction metadata.

What Is a Serialized Ledger Entry?

In brief: one typed object in ledger state, represented in memory by an SLE and in the state tree by serialized bytes plus a key.

The state SHAMap is a key-value structure. Its keys identify ledger entries; its item payloads hold their serialized fields.

State SHAMap
  └── Account-state leaf
        └── SHAMapItem
              key:   256-bit ledger entry ID
              bytes: serialized fields of one ledger entry

When application code needs to inspect these bytes, rippled deserializes them into an SLE. The object exposes typed operations such as reading an account ID or updating a sequence number.

“Account state” is the SHAMap leaf category for all ledger state entries. It includes much more than accounts:

Entry type State it represents
AccountRoot An account's XRP balance, sequence, and account settings
RippleState A trust line between two accounts for a currency
Offer An outstanding offer to exchange assets
Escrow XRP held subject to release conditions
DirectoryNode A page of references to other ledger entries
Amendments Network amendment state

One account can be associated with many ledger entries. Its trust lines and offers are separate objects, with their own keys; they are not all embedded inside its AccountRoot.

An SLE also differs from a transaction. A transaction requests an operation; ledger entries represent the state on which it operates. Transactions use STTx, and transaction metadata describes the resulting entry changes.

Why This Representation Exists

In brief: protocol state needs a shared schema, deterministic bytes, and stable identifiers.

Deterministic representation. Servers must serialize the same protocol fields and values into the same bytes. Otherwise, they could agree on an account's balance yet compute different state-tree roots because their encodings differ. Canonical serialization removes representational differences; deterministic transaction execution is also necessary to make their state agree.

Shared field machinery. Accounts, offers, and escrows have different fields, but they share infrastructure for field access, binary encoding, JSON output, and structural validation. Each entry type supplies a template instead of reimplementing those mechanisms.

Stable lookup. Application code can derive an entry's key from its identifying inputs. Changing an account's balance does not move its AccountRoot to a new position in the state tree.

Compact storage and exchange. The binary representation uses numeric field identifiers and type-specific encodings. It does not repeat JSON field names or store account addresses as Base58 strings.

Key idea. The SLE layer defines the structured contents of a state item. The SHAMap layer commits to those contents and their position in the tree.

The C++ Object

In brief: STLedgerEntry adds an entry key and ledger-entry type to the general serialized-object infrastructure.

From STLedgerEntry.h, abridged:

The fields such as Balance and Sequence belong to the inherited STObject representation. They are not separate C++ data members declared on STLedgerEntry.

The relevant layers are:

Component Responsibility
SField Describes a field's identity, serialized type, and other field properties
STUInt32, STAmount, STAccount, etc. Hold and serialize typed values
STObject Holds fields and implements object serialization and template handling
SOTemplate Describes the permitted fields and their presence rules
LedgerFormats Associates ledger-entry types with their templates
STLedgerEntry / SLE Combines an STObject with ledger-entry identity and type

Do not confuse three different uses of “type”:

  • ltACCOUNT_ROOT identifies a kind of ledger entry.
  • STI_UINT32 identifies a field's serialized value type.
  • STI_LEDGERENTRY is the high-level serialized type reported by STLedgerEntry::getSType().

The LedgerEntryType field inside an AccountRoot carries the numeric value 0x0061, encoded as a UInt16. Its JSON representation uses the readable name AccountRoot.

Schemas: Which Fields Belong Here?

In brief: a ledger entry is constrained by a template; it is not an arbitrary dictionary.

Entry definitions live in ledger_entries.macro. These are selected lines from the AccountRoot definition, with intervening optional fields omitted:

LedgerFormats.cpp adds common fields. At this revision, these are required LedgerEntryType and Flags, and optional LedgerIndex and Sponsor. This shared template is not a statement that every transaction may set every common field on every entry type.

Required, Optional, and Default

The three styles in SOTemplate.h have distinct meanings:

Style Meaning when applying a template to parsed fields
SoeRequired The field must be present.
SoeOptional The field may be absent; an explicitly present default value is allowed.
SoeDefault The field may be absent; if present, its value must be non-default.

“Default” depends on the value type. For a UInt32, it is zero. An absent MintedNFTokens field represents the default; explicitly encoding MintedNFTokens = 0 fails the SoeDefault check when the template is applied.

An absent optional field and a present field containing zero are different representations. Do not assume a zero-valued getter proves a field is present; use isFieldPresent when presence matters.

Construction Is Not Full Validation

Creating SLE(keylet) installs its template, creates default-valued required fields, and sets LedgerEntryType. It does not create a funded, protocol-valid account. The caller still has to populate meaningful values and perform the appropriate ledger operations.

When deserializing, STLedgerEntry::setSLEType finds the format from LedgerEntryType and applies its template. Unknown types, missing required fields, and disallowed non-discardable fields cause errors. SoeDefault rules are checked here too.

These are structural checks. Authorization, reserve requirements, amendment rules, valid relationships between entries, and other transaction semantics require additional validation in transaction processing and invariant checks.

Identity: Key, Contents, and Hash

In brief: the entry key identifies an object; the leaf hash commits to that object's contents at a particular state.

For an AccountRoot, the key is derived as:

accountKey = SHA512Half(
    UInt16BE(0x0061) || AccountID
)

AccountID: the decoded 20-byte identifier
UInt16BE: a two-byte integer in big-endian order
||: byte concatenation

SHA512Half means the first 256 bits of SHA-512. Here 0x0061 is the account namespace used in key derivation. Namespace identifiers and LedgerEntryType values serve separate purposes even when, as here, their numeric values match.

Use keylet::account rather than hand-implementing this formula in application code. Other entry types have their own derivation rules. A trust-line key, for example, uses both accounts in a defined order and the currency.

A Keylet combines a key with an expected entry type. A typed lookup can therefore check that the object at that key has the expected type.

The state leaf hash is a different calculation:

leafHash = SHA512Half(
    'MLN\0' || serializedEntry || entryKey
)

This is the formula used by SHAMapAccountStateLeafNode. The hash prefix is four bytes, whereas the account key namespace above is two bytes.

Change an AccountRoot balance:
  AccountID             stays the same
  entry key             stays the same
  serialized contents   change
  state leaf hash       changes, under the hash's collision-resistance assumption
  ancestor hashes       change up to the state root

The state root commits to state. It is itself one component of the ledger header; it is not the complete ledger hash.

What About index in JSON?

STLedgerEntry::getJson() adds lowercase index from key_. The deserializing constructor likewise receives the key separately from the serialized fields.

Therefore, do not model the SHAMap item's key as a mandatory field inside its payload. There is also a separately defined optional sfLedgerIndex; that does not make it interchangeable with key_ or the JSON field added by getJson().

Also distinguish an entry ID from the ledger sequence number. API ledger_index identifies a ledger version, while entry index identifies an object. See the ledger-entry common fields reference.

Serialization: Fields Become Bytes

In brief: present binary fields are sorted by numeric field identity, then each writes its identifier and encoded value.

The object is not serialized by dumping C++ memory. Pointers, padding, reference counts, and machine-native object layout are not protocol data.

STObject::add selects the fields to serialize, sorts them by field code, and writes each field's identifier followed by its value. Ordinary SLE serialization uses the all-fields path, subject to the field's binary inclusion rules.

Canonical Order

Fields are ordered first by serialized type code, then by the field number within that type. This is neither alphabetical order nor the order in which setters were called. See sfields.macro for the definitions.

For a minimal AccountRoot:

Field Type code Field number Position
LedgerEntryType 1 (UInt16) 1 1
Flags 2 (UInt32) 2 2
Sequence 2 (UInt32) 4 3
PreviousTxnLgrSeq 2 (UInt32) 5 4
OwnerCount 2 (UInt32) 13 5
PreviousTxnID 5 (UInt256) 5 6
Balance 6 (Amount) 2 7
Account 8 (AccountID) 1 8

Notice that Account appears first in the entry template but last in this binary representation.

Field Identifiers

When both the type code and field number are below 16, they fit in one byte:

field ID = (type code << 4) | field number

LedgerEntryType: (1 << 4) | 1  = 0x11
Sequence:       (2 << 4) | 4  = 0x24
OwnerCount:     (2 << 4) | 13 = 0x2D

For larger codes, a zero nibble signals an additional byte. Serializer::addFieldID emits identifiers of one, two, or three bytes. The compact header is not a general value-length header: value size is determined by the serialized type and its encoding rules.

Values

Fixed-width integers use big-endian encoding. Variable-length fields include a protocol length prefix. An ordinary populated Account field writes a length prefix followed by its 20 decoded bytes; it does not write the textual address. See STAccount::add.

STAmount has its own encoding. For a positive XRP amount, the eight-byte value contains the drop count with the positive flag set. It is not an ordinary untagged UInt64, and amounts for other asset kinds have different layouts. See STAmount::add.

Nested objects serialize their fields in canonical order. Arrays retain their element order. The enclosing serializer writes the appropriate end marker for a nested object or array; a top-level SLE payload does not acquire an outer object field header and terminator. The binary-format reference explains these encoding rules in more detail.

Encoding and Parsing Are Different Contracts

Canonical output does not mean every parsing entry point rejects noncanonical input order. At this revision, STObject::set has a requireCanonicalOrder argument that defaults to false; the SLE deserializing constructor calls set(sit) without enabling it. The parser still checks for duplicate serialized fields, and template validation follows.

When reviewing a parsing path, inspect its actual options and surrounding checks rather than assuming that “deserialized successfully” means “verified canonical input.”

Worked Example: An 87-Byte AccountRoot

In brief: a complete minimal field set makes the encoding tangible.

Use these deliberately synthetic values. This is a serialization fixture, not an account retrieved from a network or evidence of a valid ledger history:

Account:            20 bytes of 0x11
Balance:            100,000,000 drops = 100 XRP
Sequence:           1
OwnerCount:         0
Flags:              0
PreviousTxnLgrSeq:   100
PreviousTxnID:       32 bytes of 0x22
LedgerEntryType:     AccountRoot (0x0061)

Its serialized fields are:

Field Header and value bytes Total bytes
LedgerEntryType 11 00 61 3
Flags 22 00 00 00 00 5
Sequence 24 00 00 00 01 5
PreviousTxnLgrSeq 25 00 00 00 64 5
OwnerCount 2D 00 00 00 00 5
PreviousTxnID 55 followed by 32 bytes of 22 33
Balance 62 40 00 00 00 05 F5 E1 00 9
Account 81 14 followed by 20 bytes of 11 22
Total payload Concatenate the rows in order 87

The balance's 0x4000000000000000 flag marks it nonnegative; 0x05F5E100 is 100,000,000. The 0x14 after the Account field header is its 20-byte length.

The 87 bytes exclude the SHAMap item's key and any SHAMap node framing. Here is a runnable Python reproduction of this specific fixture, using the encoding rules above. It is not a general XRPL codec:

This shows why three operations must remain distinct: deriving the account key, serializing the fields, and hashing the leaf.

Reading and Changing Ledger Entries

In brief: transaction code accesses state through ledger views, which record changes before those changes reach the underlying ledger.

Read-Only Access

A ReadView exposes read(Keylet const&), returning std::shared_ptr<SLE const>.

Illustrative code inside a function with a ReadView const& view and AccountID const& account:

auto const sle = view.read(keylet::account(account));
if (!sle)
    return;  // No matching AccountRoot in this view.

auto const balance = sle->getFieldAmount(sfBalance);
auto const sequence = sle->getFieldU32(sfSequence);

For a concrete ledger, the underlying path looks up the SHAMap item and constructs an SLE using both the item's bytes and its key. View layers and caches can serve the read without repeating that entire path on every call.

Mutable Access

ApplyView provides the transaction-facing operations:

Operation Purpose
peek(keylet) Obtain a mutable entry associated with this view, if it exists and matches.
update(sle) Record a modification to an existing entry.
insert(sle) Record a new entry.
erase(sle) Record removal of an existing entry.

An illustrative field update, inside transaction application code that has already validated the change:

auto sle = view.peek(keylet::account(account));
if (!sle)
    return;  // Real transaction code supplies the appropriate result.

sle->setFieldU32(sfSequence, newSequence);
view.update(sle);

newSequence here is a value computed under the transaction's rules, not permission to change sequence numbers arbitrarily.

The update call matters: changing a C++ object does not by itself complete a ledger mutation. Follow the view contract even if the implementation currently holds a pointer to that same object. Similarly, after erasing an entry, treat it as removed rather than continuing to mutate it through a retained pointer.

Views allow transaction application to stage changes. Whether staged effects are committed, discarded, or replaced by a fee-only result depends on the transaction result and application path. Avoid the blanket rule “every failed transaction makes no state changes”: some included failures still charge a fee.

From View Changes to SHAMap Items

When changes reach the concrete Ledger, insertion and replacement serialize the SLE and create a SHAMap item using its key. Erasure removes the item by key.

Transaction application
  → read / peek entries
  → validate and compute field changes
  → update / insert / erase in the view
  → apply the staged result to the target view or ledger
  → serialize inserted or replaced SLEs
  → change keyed items in the state SHAMap
  → recompute affected hashes as the tree is finalized/hashed

The SHAMap's copy-on-write machinery protects shared tree snapshots. The ledger views manage application-level entry changes. These are related mechanisms at different layers.

Also separate updating the state tree from persisting nodes to NodeStore: an SLE setter is not a database write, and an SLE is not itself a NodeStore record.

Entries and Transaction Metadata

In brief: the state tree records the resulting objects; metadata describes how a transaction affected them.

A payment may affect multiple entries. Its metadata identifies the changed entries under AffectedNodes, using wrappers such as:

  • CreatedNode: an entry was created.
  • ModifiedNode: an existing entry changed.
  • DeletedNode: an entry was removed.

Each affected node identifies the entry by LedgerIndex and LedgerEntryType. Depending on the change and the field's metadata policy, it includes NewFields, FinalFields, or PreviousFields.

Metadata is not simply a complete copy of every entry before and after the transaction. In particular, PreviousFields records selected previous values of changed fields. Field metadata flags influence what is emitted. Trace this in ApplyStateTable.cpp and the field definitions in sfields.macro.

For a closed ledger, transaction metadata is stored alongside its transaction in the transaction SHAMap. It is not an additional SLE in the state SHAMap. TxMeta.cpp shows how affected-node metadata receives the entry key as LedgerIndex.

For entry types that support transaction threading, PreviousTxnID and PreviousTxnLgrSeq link the current entry to the transaction that most recently changed it and the ledger containing that transaction. They do not embed the previous version of the entry. Eligibility for threading can depend on amendment rules; inspect STLedgerEntry::isThreadedType rather than assuming every entry is treated identically.

State SHAMap:         What does this entry contain now?
Transaction metadata: What did this transaction create, change, or delete?

Practical Exercises

≈30–40 min · use the pinned source revision or record the revision you investigate.

1. Follow an AccountRoot Through the Source

Find its definition in ledger_entries.macro, then locate each required field in sfields.macro.

Explain why Account comes after Balance in the serialized payload, even though it appears before Balance in the template. Identify which required fields come from the common template.

Expected result: ordering comes from numeric serialized field identity; LedgerEntryType and Flags are the required common fields at this revision.

2. Change a Value Without Changing Identity

Run the Python fixture. Change the balance from 100 XRP to 99 XRP, using (0x4000000000000000 | 99_000_000).to_bytes(8, "big") for its value bytes. Recompute the key and leaf hash, replacing the original leaf-hash assertion with a comparison.

Expected result: the key is unchanged; the leaf hash differs; the payload remains 87 bytes.

3. Test Template Behavior in rippled

Using the patterns in STObject_test.cpp, build and serialize the fixture in C++, then deserialize it with its key. Compare the serialized bytes to the Python fixture.

Next, construct parsed-object variants before applying the SLE template:

  • Omit required Sequence.
  • Include MintedNFTokens explicitly as zero.
  • Include MintedNFTokens as one.

Expected result: the first two variants fail template validation; the third passes those structural checks. This does not establish that the third describes a valid account history.

Use parsed fields or an untemplated STObject for the missing-field case: constructing a fresh typed SLE automatically creates its required fields.

4. Trace One Real Mutation

Choose a transaction implementation that uses peek and update. Record the entry's keylet, changed fields, and where the view receives the update. Follow the change into ApplyStateTable, then into the concrete ledger's replacement operation.

Expected result: you can point to the boundary between changing fields, recording a view modification, and serializing the replacement state item.

Appendix: Codebase Navigation Guide

In brief: read from the object model outward to serialization and ledger integration.

Start here What to inspect
STLedgerEntry.h and STLedgerEntry.cpp Identity, constructors, template application, JSON, threading
ledger_entries.macro Entry types and their field templates
LedgerFormats.cpp Registry construction and common fields
SField.h and sfields.macro Field identifiers, types, and metadata policies
STObject.cpp Parsing, presence, template validation, canonical output
Serializer.cpp Field headers, byte writing, length prefixes
Indexes.cpp Key derivation and keylet helpers
ReadView.h and ApplyView.h Read and mutation contracts
ApplyStateTable.cpp Staged changes and metadata generation
Ledger.cpp Conversion between SLEs and state SHAMap items

For encoding tests, also read Serializer_test.cpp. For API behavior, inspect LedgerEntry_test.cpp.

To Remember

  • An SLE is rippled's typed in-memory representation of one ledger state entry.
  • Its template defines field presence rules; transaction processing supplies the semantic rules.
  • The entry key identifies the object. Canonical serialization encodes its fields. The state leaf hash commits to both.
  • read gives const access; mutable view operations explicitly record insertions, modifications, and deletions.
  • The state SHAMap stores the result, while transaction metadata describes the affected entries.

Next up. We’ve seen how ledger entries become serialized bytes in the SHAMap. Next, we’ll follow that state into persistent storage: how rippled’s NodeStore and database backends store and retrieve tree nodes, and how database operations support the ledger’s lifecycle.

Trace an SLE Through rippled

Format: Group investigation
Duration: 45–60 minutes, plus approximately 3 minutes per group for presentations
Level: Advanced — XRPL Core Developer Bootcamp
Repository: XRPLF/rippled

Objective

Each group investigates a different Serialized Ledger Entry (SLE) type: representation, access, modification, serialization, deserialization, and storage in the state SHAMap. Compare your findings to identify shared infrastructure and entry-specific behavior.

Your task is to navigate source code and observe execution. Keep transaction rules and test assertions intact. Do not manufacture an artificial lifecycle or change fields outside the protocol just to produce a log.

Learning objectives

By the end, you should be able to:

  1. Locate an entry's schema, identity, key derivation, and transaction implementations.
  2. Trace construction, reading, and deserialization, including paths that reuse cached objects.
  3. Explain how mutable ledger views record changes.
  4. Identify the boundary between typed C++ fields and serialized bytes.
  5. Follow insertion, replacement, or removal in the state SHAMap and investigate the resulting hashes.
  6. Distinguish in-memory SLEs, staged changes, tree updates, and persistent storage.

Prerequisites and current revision

Complete the SHAMap and SLE presentations and prepare a working Debug development build with unit tests using the Build & run your own xrpld node prerequisite. Be comfortable with C++ navigation, breakpoints or focused logging, and rebuilding your configured target.

Use the latest rippled version prepared for the bootcamp session. An older version number in the prerequisite is not the version requirement for this exercise. At the 13 October 2026 check, the latest stable release is 3.4.1. The source links below are fixed to its public release commit 00e640751460c137636fdb6dae6b4280de355d81 so line references remain reproducible.

When the session moves to a newer revision, the instructor should rerun the scenarios and update these source references. Record your own checkout revision, executable version, build configuration, and actual commands in the report; investigate any differences from linked sources.

Build and test workflow

Reuse the Debug build already configured in the prerequisite or supplied by the instructor. In the prerequisite's existing build directory, its documented rebuild command is:

cmake --build . --parallel 10

Use your configured build directory, target, and parallelism; rebuild after instrumenting. This is the course's documented rebuild command. Validation below used a packaged executable, not a fresh source build.

Run only your group's suite using your actual executable path. The examples assume the prerequisite's ./xrpld layout; some configurations use ./Debug/xrpld. The suite argument is the same:

./xrpld --version
./xrpld --unittest=AccountSet

Replace AccountSet with your group's suite. Fixtures provide deterministic accounts without a faucet or running node. Suites execute additional scenarios: filter logs by entry key, case, and operation. The commands select suites, not individual C++ test methods.

Scenario validation: the packaged xrpld 3.4.1 executable successfully ran AccountSet,TrustSet,Offer,Escrow,Check,PayChan (10 suites, 362 cases, 51,408 tests, 0 failures) and TrustAndBalance (1 suite, 24 cases, 1,269 tests, 0 failures). Offer also selects related offer suites. The packaged binary's build commit differs from the public release commit; selected source files were checked for correspondence. This is a runtime baseline, not an instrumented trace or fresh source build. Recheck the fixtures in the cohort's Debug build before the session.

Group setup

The instructor chooses 1–6 groups and assigns a different entry type to each group. With fewer than six groups, select a subset. Organize participants separately from the entry-type assignment and have each group record its number, members, and assigned type.

Each group follows one assignment below and completes Parts 1–4. Common instructions apply to every group; there is no requirement to complete the other five scenarios.

Entry type Controlled scenario Unit-test suite
AccountRoot Fund an account, then set its Domain with AccountSet AccountSet
RippleState Create a USD trust line, then change its nonzero limit TrustAndBalance
Offer Create an offer that rests on the book, then cancel it OfferBaseUtil
Escrow Create an escrow, then finish or cancel it using the fixture's controlled time Escrow
Check Create a check, then cancel it Check
PayChannel Create a payment channel, then add funds PayChan

AccountRoot assignment

Use AccountSet_test::testDomain. The fixture funds alice with XRP and then submits an AccountSet setting Domain to example.com. Trace the funding-created entry and the first successful Domain-setting transaction. Later transactions clear the Domain or test its size limits; keep those separate from your main trace.

./xrpld --unittest=AccountSet

Optional navigation hint: follow the fixture's noop(alice) helper to its transaction type, then explore AccountSet.cpp and the account-creation branch in Payment.cpp. Which changes belong to settings, and which belong to fees or sequence handling?

RippleState assignment

Use TrustAndBalance_test::testCreditLimit. Reuse gateway (gw), alice, and the fixture's funding/ledger-close setup. Focus on the first USD trust line from Alice to the gateway with limit 800, followed by a TrustSet changing that limit to 700. Preserve the existing assertions that check the line after each transaction. The later negative-limit and zero-limit cases are outside this group's required trace.

./xrpld --unittest=TrustAndBalance

Optional navigation hint: inspect the trust transaction helper, TrustSet.cpp, and Indexes.cpp. Determine which endpoint is low/high from the actual account IDs. Which limit field changes, and how do you establish that you are still following the same entry?

Offer assignment

Use OfferBaseUtil_test::testCanceledOffer. Reuse gateway (gw), alice, the USD trust line, funding, and initial offers. Focus on the final sequence using offer4Seq: create the offer with XRP(222) and usd(111), close the ledger, verify isOffer(...), then cancel that sequence and verify its absence.

./xrpld --unittest=OfferBaseUtil

The fixture has no opposing offers for this scenario. Keep the offer-existence assertions: transaction success alone is insufficient evidence that an offer rested on the book or that the cancellation removed your target.

Optional navigation hint: start at the DEX transactors (OfferCreate, OfferCancel) and OfferHelpers.cpp. Follow creation and deletion naturally; an in-place modification is not required.

Escrow assignment

Use Escrow_test::testTiming. Reuse funded alice and bob and the fixture's XRP(1000) escrow. The instructor selects one existing successful branch: Finish Only, or Cancel Only. Keep its exact FinishAfter/CancelAfter values, sequence references, and env.close(...) time advances. These are harness-controlled conditions; do not substitute wall-clock sleeping.

./xrpld --unittest=Escrow

Optional navigation hint: follow the escrow transactors (EscrowCreate, EscrowFinish, EscrowCancel). If investigating cryptographic conditions as an extension, reuse the matching condition/fulfillment pairs from testEscrowConditions in the same test file and escrow.h; do not invent a pair.

Check assignment

Use Check_test::testCancelValid. Reuse the funded gateway, alice, bob, and zoe fixtures. Choose an existing successful Alice-to-Bob creation and cancellation by Alice or Bob, preserving its SendMax, sequence, and assertions. Focus on the unexpired authorized-cancellation path; later third-party and expiration cases are separate observations.

./xrpld --unittest=Check

Optional navigation hint: explore the check transactors (CheckCreate, CheckCancel). What must be true for cancellation to succeed, and which ledger entries are affected besides the check?

PayChannel assignment

Use PayChan_test::testSimple. Reuse alice, bob, Alice's fixture public key, the 100-second settle delay, and the channel key captured from the creation sequence. Trace creation with XRP(1000), then PaymentChannelFund adding XRP(1000) to that same channel. Preserve the assertions around Amount and Balance.

./xrpld --unittest=PayChan

Optional navigation hint: start at the payment-channel transactors (PaymentChannelCreate, PaymentChannelFund). Which fields distinguish funding from a claim? Which values identify the channel across both transactions?

Part 1 — Understand your entry

Locate your assigned entry's schema and transaction implementation. Investigate:

  • What protocol state does this entry represent?
  • Which fields are required or optional? Which fields matter to your scenario?
  • Where is the schema defined, and how does an SLE acquire its ledger-entry type?
  • How is the 256-bit ledger-entry key computed? Is it the same as an account address?
  • Which transactions can create, modify, or delete this type?

Expected result: a brief description of the entry, its important fields, its key derivation, and its supported lifecycle operations.

Optional navigation hint: use ledger_entries.macro for the schema, then follow the relevant keylet through Indexes.h and Indexes.cpp. Use your assigned transaction's call sites to keep the search focused.

Part 2 — Trace the entry in rippled

Run the assigned scenario with focused logs or breakpoints. Record the target key and selected field values so that incidental AccountRoot, directory, or other entry changes do not become confused with your assigned object.

Find and observe, where applicable:

  1. Creation: where is the SLE constructed and given its identity?
  2. Reading: how does application code retrieve it? Compare read-only and mutable access.
  3. Deserialization: where can serialized bytes become a typed SLE?
  4. Modification: which fields change, and where is the change recorded in a ledger view?
  5. Serialization: where are the fields converted to binary bytes? Record the payload size.
  6. SHAMap update: where is the keyed item inserted, replaced, or removed? Follow the effect on tree hashes.

Not every operation must occur. An Offer or Check may be created and deleted; a cached read may not decode bytes. If a stage is absent, investigate the reason. An isolated serialization/deserialization test is allowed, but label it separately from the transaction trace.

Use these C++ symbols as navigation starting points; follow the concrete implementation used by your scenario:

STLedgerEntry
SerialIter
STObject
Serializer
ReadView::read(...)
ApplyView::peek(...)
ApplyView::insert(...)
ApplyView::update(...)
ApplyView::erase(...)
SHAMapItem

Shared questions to investigate

  • What does a read-only view return? How does peek() differ from read()?
  • What information does SerialIter provide, and how are fields reconstructed into typed values?
  • How does STLedgerEntry determine its entry type? Why might a read skip decoding?
  • Does a field setter immediately change the SHAMap? What does the view's update operation record?
  • Why can the key remain unchanged when the contents change? Is that key an ordinary field in the serialized payload?
  • Why must serialization be deterministic? What changes for your operation: key, payload, leaf hash, tree structure, or several of these?
  • When are the affected hashes recomputed? What causes the state root to change?
  • Does a SHAMap update immediately mean a disk write? Which extra steps connect the tree to persistent storage?

Use source references and observations to support your answers. Implementing the hashing algorithm is not required.

Part 3 — Reconstruct the lifecycle

Draw the actual execution path your group followed. Include the relevant C++ functions/classes and label these boundaries:

  • The typed in-memory SLE.
  • Staged insertions, modifications, or deletions in ledger views.
  • The serialized binary payload.
  • The SHAMap item associated with the ledger-entry key.
  • Changes to the state SHAMap and its hashes.

Mark each transition observed in logs/breakpoints, established by source inspection, or not observed, with an explanation. Include caches and intermediate views when they appear in your trace. Creation, modification, deletion, and loading do not have to form one continuous path.

Recording template (not executed evidence):

<order> | <entry key> | <C++ function> | <source file:line>
        | <selected fields / payload size / tree operation>
        | <observed / source inspection / not observed and why>

Part 4 — Prepare group findings

Prepare an approximately 3-minute technical presentation covering:

  1. The entry: what does it represent, and what identifies it?
  2. Its lifecycle: which transactions create, modify, or delete it?
  3. The C++ trace: which functions did you instrument, and what did the logs reveal?
  4. Serialization: where do fields become bytes, and when might bytes become an object?
  5. SHAMap integration: what changed in the state tree?
  6. Unexpected findings: were operations skipped, was the entry deleted instead of updated, or did other entry types change in the same transaction?

Deliverables

Each group submits one shared report or report link containing:

  1. The group number, members, assigned entry type, checkout revision, executable version, and actual build/test commands.
  2. A lifecycle diagram with relevant C++ functions and source references.
  3. Selected logs or breakpoint observations demonstrating significant operations, in chronological order.
  4. A short explanation of entry identity, binary representation, ledger views, SHAMap integration, caching, and persistence.
  5. A summary of stages observed, established by inspection, or not observed and why.

Keep excerpts focused; do not upload thousands of lines of raw logs. Use the existing mentor-reviewed report assignment. Academy records submissions and completion per learner: a representative's submission does not automatically credit teammates. If every member needs completion recorded, each can submit the same shared report link with their name and group.

Cross-group comparison

After the presentations, compare the evidence across groups. Discuss:

What was different? Entry schemas, key derivation, transaction rules, supported lifecycle operations, and the specific fields changed.

What was shared? SLE representation, typed field infrastructure, serialization machinery, ledger views, and SHAMap integration.

Support comparisons with code or observations. Investigate whether caching, intermediate views, or scenario differences explain different paths through shared infrastructure.

Optional source navigation

Follow components relevant to your trace and record differences when the cohort upgrades.

Component Investigation starting point
SLE identity and decoding STLedgerEntry.cpp
Typed fields and serialization STObject.cpp
Binary writing and reading Serializer.h (including SerialIter)
Ledger access contracts ReadView.h / ApplyView.h
Intermediate views and staged actions ApplyStateTable.cpp
Concrete ledger reads and raw updates Ledger.cpp
Keyed serialized item SHAMapItem.h
State tree operations and flushing SHAMap.cpp
Leaf and inner-node hashes SHAMapAccountStateLeafNode.h / SHAMapInnerNode.cpp

Completion criteria

The investigation is complete when your group can explain its entry's identity and actual lifecycle with code references and selected observations, locate the structured-fields/serialized-bytes boundary, and distinguish staged changes, SHAMap changes, and persistent storage.

Explain absent operations instead of inventing a trace. You are not expected to observe every lifecycle stage in a single transaction or demonstrate creation, modification, and deletion for every type.

Optional extensions

  1. Investigate a different supported lifecycle operation for your assigned type.
  2. Compare the serialized payload before and after a change, where both states exist.
  3. Recompute a state leaf hash using the correct hash prefix, payload, and entry key.
  4. Compare the resulting state with the appropriate CreatedNode, ModifiedNode, or DeletedNode in transaction metadata.

Final question

At what exact points did your entry transition between structured C++ fields and serialized bytes, which components were responsible, and which of those mechanisms were shared with the other groups?

Different entry types share technical infrastructure. Use these findings to navigate unfamiliar types.

Assignments

0 of 1 complete