mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 16:56:48 +00:00
regen skills
This commit is contained in:
@@ -11,7 +11,9 @@ XRPL supports secp256k1 (ECDSA) and ed25519 key types. All crypto uses OpenSSL +
|
||||
- Base58 encoding includes a type byte prefix and 4-byte checksum (double SHA-256)
|
||||
- All randomness for cryptographic material flows through `crypto_prng()`; never call OpenSSL's `RAND_bytes` directly and never use `std::rand`/`rand()`
|
||||
- `csprng_engine` is non-copyable and non-movable by deleted ops; the singleton must be accessed by reference via `crypto_prng()`
|
||||
- `csprng_engine` satisfies the C++ *UniformRandomNumberEngine* named requirement (`result_type` = `std::uint64_t`, `operator()()`, `constexpr min()`/`max()`) — plugs into `std::uniform_int_distribution`, `beast::rngfill`, etc.
|
||||
- RFC 1751 dictionary has exactly 2^11 = 2048 entries; entries 0–570 are 1–3 char words, 571–2047 are exactly 4 chars (used to split binary search range in `wsrch`)
|
||||
- Each RFC 1751 word encodes exactly 11 bits; a 64-bit block uses 6 words (66 bits = 64 data + 2 parity); a 128-bit key uses two such blocks → 12 words
|
||||
|
||||
## Common Bug Patterns
|
||||
|
||||
@@ -24,6 +26,9 @@ XRPL supports secp256k1 (ECDSA) and ed25519 key types. All crypto uses OpenSSL +
|
||||
- Constructing a second `csprng_engine` instance: forbidden by deleted ctors; sharing one OpenSSL pool through the singleton is required
|
||||
- Passing `mix_entropy` a buffer and assuming OpenSSL credits it as entropy — the entropy estimate is always 0 (deliberately conservative)
|
||||
- RFC 1751 decode: distinguish `0` (unknown word), `-1` (malformed input), `-2` (parity failure) — don't collapse all failures into a single error
|
||||
- Forgetting that `insert()` in RFC1751 uses bitwise OR, not assignment — output buffer must start zero-initialized
|
||||
- Treating RFC 1751 parity as cryptographic integrity — it's a 2-bit transcription check, not a MAC
|
||||
- Using `getWordFromBlob` for anything cryptographic — it's a Jenkins hash and explicitly insecure
|
||||
|
||||
## Review Checklist
|
||||
|
||||
@@ -32,6 +37,7 @@ XRPL supports secp256k1 (ECDSA) and ed25519 key types. All crypto uses OpenSSL +
|
||||
- Verify that key type dispatch handles both secp256k1 and ed25519 (or explicitly rejects one with a clear error)
|
||||
- Any new sensitive type should follow the `SecretKey`/`Seed` pattern: destructor calls `secure_erase` as its first/only action
|
||||
- New OpenSSL touchpoints should respect the `OPENSSL_VERSION_NUMBER < 0x10100000L` thread-safety guard pattern used in `csprng.cpp`
|
||||
- CSPRNG failures (`RAND_bytes`/`RAND_poll` ≠ 1) must propagate via `Throw<>` (logs stack trace) — never silently fall back
|
||||
|
||||
## Key Patterns
|
||||
|
||||
@@ -49,7 +55,7 @@ SecretKey sk(Slice{buf, sizeof(buf)});
|
||||
secure_erase(buf, sizeof(buf)); // MUST erase raw buffer
|
||||
```
|
||||
|
||||
`secure_erase` delegates to `OPENSSL_cleanse`, which uses volatile writes / opaque function-pointer calls to defeat dead-store elimination. Lives in a separate TU (`secure_erase.cpp`) so the call site cannot inline it away. It does **not** clear CPU registers or caches — it is best-effort for heap/stack only (see Percival 2014).
|
||||
`secure_erase` delegates to `OPENSSL_cleanse`, which uses volatile writes / opaque function-pointer calls to defeat dead-store elimination. Lives in a separate TU (`secure_erase.cpp`) so the call site cannot inline it away — the out-of-line call alone forces the compiler to treat it as an opaque side effect. It does **not** clear CPU registers or caches — it is best-effort for heap/stack only (see Percival 2014). Takes raw `void*` + byte count with no null/zero guards; callers must supply valid arguments.
|
||||
|
||||
### CSPRNG Usage
|
||||
```cpp
|
||||
@@ -64,7 +70,7 @@ rng(buf, sizeof(buf)); // operator()(void*, size_t)
|
||||
beast::rngfill(buf, sizeof(buf), crypto_prng());
|
||||
```
|
||||
|
||||
`csprng_engine` satisfies the C++ *UniformRandomNumberEngine* named requirement, so it plugs directly into `std::uniform_int_distribution` and similar. Failure (insufficient entropy) throws `std::runtime_error` via `Throw<>`; callers generally do not catch — propagation halts the operation, which is correct.
|
||||
Failure (insufficient entropy) throws `std::runtime_error("CSPRNG: Insufficient entropy")` via `Throw<>`; callers generally do not catch — propagation halts the operation, which is correct. Generating cryptographic material from an entropy-exhausted pool would be worse than crashing.
|
||||
|
||||
### Key Type Dispatch
|
||||
```cpp
|
||||
@@ -90,6 +96,8 @@ int rc = RFC1751::getKeyFromEnglish(roundTrip, words);
|
||||
|
||||
`Seed.cpp` reverses the 16 bytes before/after RFC 1751 encoding to match the RFC's big-endian convention. `standard()` normalizes input by uppercasing and applying visual substitutions `1→L`, `0→O`, `5→S` for handwritten/OCR tolerance. The 2-bit parity per 8-byte half is a transcription check, **not** a cryptographic integrity check.
|
||||
|
||||
`getKeyFromEnglish` uses `boost::algorithm::split` with `token_compress_on` for whitespace tolerance. The asymmetry between encoder (no return code — cannot fail on valid input) and decoder (4-valued return code) is intentional: encoding is lossless, decoding must validate user input.
|
||||
|
||||
`getWordFromBlob` is a separate utility: Jenkins one-at-a-time hash → `% 2048` → one dictionary word. Explicitly **not** cryptographically secure; used in `NetworkOPs.cpp` for `shroudedHostId` (privacy-preserving node label in logs/RPC).
|
||||
|
||||
## Module Layout
|
||||
@@ -108,26 +116,29 @@ These three are used together by the protocol-level key/seed types (`SecretKey`,
|
||||
|
||||
- Constructor calls `RAND_poll()` eagerly to surface OS entropy failures at startup, not at first key gen
|
||||
- Destructor calls `RAND_cleanup()` only for OpenSSL `< 1.1.0` (modern versions clean up via `atexit`)
|
||||
- Thread-safety mutex is compile-time gated: `#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || !defined(OPENSSL_THREADS)` — modern builds elide the lock on the hot path because `RAND_bytes` is internally thread-safe
|
||||
- `mix_entropy` always holds the mutex around `RAND_add`; reads from `std::random_device` happen before locking (independently thread-safe)
|
||||
- `mix_entropy` passes entropy estimate `0` to `RAND_add` — never claim entropy for `std::random_device` or caller-supplied buffers (they may be weak on some platforms)
|
||||
- Thread-safety mutex is compile-time gated: `#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || !defined(OPENSSL_THREADS)` — modern builds elide the lock on the hot path because `RAND_bytes` is internally thread-safe. The mutex is *always* held around `RAND_add` in `mix_entropy` regardless of version.
|
||||
- `mix_entropy` reads 128 values from `std::random_device` *before* locking (independently thread-safe), then locks for `RAND_add`
|
||||
- `mix_entropy` passes entropy estimate `0` to `RAND_add` — never claim entropy for `std::random_device` or caller-supplied buffers (they may be weak on some platforms; conservative accounting prevents prematurely satisfying OpenSSL's seeding threshold)
|
||||
- Called on a timer from `Application.cpp` to stir fresh OS entropy during the node's lifetime
|
||||
- Singleton is a function-local `static` (Meyers singleton); C++11 guarantees thread-safe one-time init
|
||||
- Scalar `operator()()` delegates to buffer-fill overload with `sizeof(result_type)` (8 bytes) — both paths share validation/error handling
|
||||
- Wrapping behind a single `xrpl::secure_erase` function lets the strategy change (e.g., to `explicit_bzero`) at one auditable choke point without touching call sites
|
||||
|
||||
### RFC 1751 Internals Worth Knowing
|
||||
|
||||
- `extract(s, start, length)` / `insert(s, x, start, length)`: read/write `length ≤ 11` bits at arbitrary offset across a 9-byte buffer; guarded by `XRPL_ASSERT` (stripped in release)
|
||||
- `extract(s, start, length)` / `insert(s, x, start, length)`: read/write `length ≤ 11` bits at arbitrary offset across a 9-byte buffer; guarded by `XRPL_ASSERT` (stripped in release). Both work across byte boundaries by assembling 2–3 adjacent bytes.
|
||||
- `insert` uses bitwise OR (not assignment), so the output buffer must start zero-initialized; partial writes accumulate safely
|
||||
- `btoe` adds a 9th byte for the 2-bit parity computed by summing all 32 pairs of bits across the 64-bit payload; parity occupies bit positions 64–65
|
||||
- `etob` validates: exactly 6 words, each 1–4 chars, all in dictionary, parity matches — distinct error codes per failure mode
|
||||
- `getKeyFromEnglish` uses `boost::algorithm::split` with `token_compress_on` for whitespace tolerance
|
||||
- `etob` validates: exactly 6 words, each 1–4 chars, all in dictionary, parity matches — distinct error codes per failure mode (`0` unknown, `-1` malformed, `-2` parity)
|
||||
- `wsrch` halves the binary search range based on input word length: `[0, 571)` for 1–3 char words, `[571, 2048)` for 4-char words
|
||||
- No exceptions used anywhere in RFC1751 — all errors are integer return codes
|
||||
|
||||
## Key Files
|
||||
|
||||
- `include/xrpl/protocol/SecretKey.h` / `PublicKey.h` — key types
|
||||
- `src/libxrpl/protocol/SecretKey.cpp` — signing, key generation; canonical example of CSPRNG + `secure_erase` discipline
|
||||
- `src/libxrpl/protocol/PublicKey.cpp` — verification
|
||||
- `src/libxrpl/protocol/Seed.cpp` — 128-bit seed; uses RFC 1751 for mnemonic encoding
|
||||
- `src/libxrpl/protocol/Seed.cpp` — 128-bit seed; uses RFC 1751 for mnemonic encoding (reverses bytes for big-endian convention)
|
||||
- `include/xrpl/protocol/digest.h` — hash functions (`sha512Half`, `ripesha_hasher`, etc.)
|
||||
- `include/xrpl/crypto/csprng.h` + `src/libxrpl/crypto/csprng.cpp` — CSPRNG engine and singleton
|
||||
- `include/xrpl/crypto/secure_erase.h` + `src/libxrpl/crypto/secure_erase.cpp` — memory wipe primitive
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Protocol and Serialization
|
||||
|
||||
The protocol layer defines XRPL's wire format, type system, and validation rules. It owns the canonical binary encoding required for signatures and consensus, the macro-driven registries for features/transactions/ledger entries, and the typed object model (`STBase` hierarchy) that every transaction and ledger object inhabits.
|
||||
The protocol layer defines XRPL's wire format, type system, and validation rules. It owns the canonical binary encoding required for signatures and consensus, the macro-driven registries for features/transactions/ledger entries/sfields/permissions, the typed object model (`STBase` hierarchy) that every transaction and ledger object inhabits, the cryptographic primitives, and the JSON/RPC boundary.
|
||||
|
||||
## Layered Type System
|
||||
|
||||
@@ -19,19 +19,27 @@ STAmount = unified wire/serialized form holding Asset + value
|
||||
- canonicalize() normalizes (mantissa, exponent) per asset rules
|
||||
```
|
||||
|
||||
`PathAsset` = `std::variant<Currency, MPTID>` — pathfinding-only asset reference (no issuer); used inside `STPathElement`.
|
||||
|
||||
Conversion utilities in `AmountConversions.h`: `toSTAmount`, `toAmount<T>`, `getAsset<T>`. Lean→STAmount is implicit-friendly; STAmount→lean is explicit (`get<>` throws on type mismatch).
|
||||
|
||||
`Units.h` provides phantom-typed `ValueUnit<TAG, T>` (`Drops`, `FeeLevel`, `FeeLevelDouble`) with `unit_cast<>` for explicit conversion; prevents drop/fee-level mixups at compile time.
|
||||
|
||||
## Key Invariants
|
||||
|
||||
- **Canonical field ordering:** sort by `(SerializedTypeID << 16) | fieldValue`, NOT by raw Field ID bytes — wrong sort breaks signatures
|
||||
- **Field ID encoding:** 1–3 bytes; both type and field codes <16 → single byte `(type<<4)|name`
|
||||
- **Hash domain separation:** every signable payload prepends a 4-byte `HashPrefix` (`STX\0`, `SMT\0`, `VAL\0`, `STX\0`, `BCH\0`, `CLM\0`, `LWR\0`, etc.) — never share hashes across domains
|
||||
- **STObject access semantics:** `obj[sfFoo]` throws `FieldErr` if absent; `obj[~sfFoo]` returns `std::optional`
|
||||
- **Amendment IDs are deterministic:** `featureFoo == sha512Half(Slice("Foo"))` — never change a feature name
|
||||
- **Singletons everywhere:** `SField`, `LedgerFormats`, `TxFormats`, `InnerObjectFormats`, `Permission`, `Feature` registry all use Meyer's singletons; registration completes before `main()` via static init
|
||||
- **Multi-sign signers MUST be sorted ascending by AccountID** (no duplicates, count in [1,32], cannot include tx account)
|
||||
- **`vfFullyCanonicalSig` always set** by signer; verifiers normalize ECDSA S to low form
|
||||
- **Amendment-gated arithmetic:** `getSTNumberSwitchover()` is a `LocalValue<bool>` (per-coroutine) selecting legacy vs `Number`-based normalization in `IOUAmount`/`STAmount`
|
||||
- **Hash domain separation:** every signable payload prepends a 4-byte `HashPrefix` (`STX\0`, `SMT\0`, `VAL\0`, `BCH\0`, `CLM\0`, `LWR\0`, `MAN`, `TXN`, etc.) — never share hashes across domains. Helper `make_hash_prefix(a,b,c)` is constexpr `uint32_t` builder.
|
||||
- **STObject access semantics:** `obj[sfFoo]` throws `FieldErr` if absent; `obj[~sfFoo]` returns `std::optional`. `getOrThrow<T>(name)` family in `json_get_or_throw.h` enforces presence + type for raw JSON inputs.
|
||||
- **Amendment IDs are deterministic:** `featureFoo == sha512Half(Slice("Foo"))` — never change a feature name. Feature names must satisfy `isFeatureName()` at compile time (`UpperCamel` regex).
|
||||
- **`numFeatures` is a ceiling, NOT an exact count.** Counting includes `XRPL_RETIRE_*` and any inactive macros; never use it as a length.
|
||||
- **Feature registry frozen at startup:** `registerFeature` checks `numFeatures` and aborts via static-init `LogicError` if exceeded.
|
||||
- **Singletons everywhere:** `SField`, `LedgerFormats`, `TxFormats`, `InnerObjectFormats`, `Permission`, `Feature` registry all use Meyer's singletons; registration completes before `main()` via static init.
|
||||
- **Multi-sign signers MUST be sorted ascending by AccountID** (no duplicates, count in [1,32], cannot include tx account).
|
||||
- **`vfFullyCanonicalSig` always set** by signer; verifiers normalize ECDSA S to low form via libsecp256k1.
|
||||
- **Amendment-gated arithmetic:** `getSTNumberSwitchover()` is a `LocalValue<bool>` (per-coroutine) selecting legacy vs `Number`-based normalization in `IOUAmount`/`STAmount`.
|
||||
- **TxMeta `AffectedNodes` must be sorted by index** for canonical serialization (consensus-critical).
|
||||
- **STObject debug-only field-uniqueness checks** (`isFieldAllowed`): silent duplicate fields in production are possible bugs but no runtime check.
|
||||
|
||||
## Macro-Driven Registries (X-Macros)
|
||||
|
||||
@@ -47,6 +55,8 @@ Single source of truth for each registry; `.macro` files included multiple times
|
||||
|
||||
Pattern uses `#pragma push_macro/pop_macro` to protect macro names. `UNWRAP(...)` strips outer parens around field-list initializers so commas don't confuse the preprocessor. `LEDGER_ENTRY_DUPLICATE` exists because `DepositPreauth` is both a transaction type and ledger entry type — `JSS()` can't emit the same string twice.
|
||||
|
||||
Feature name validation is `constexpr` (compile-time `static_assert` on the literal); typos like lower-case first letter fail to build.
|
||||
|
||||
## Field Identity (`SField`)
|
||||
|
||||
- **Field code** = `(SerializedTypeID << 16) | fieldValue` — packs type family and per-type index; canonical sort key
|
||||
@@ -55,6 +65,7 @@ Pattern uses `#pragma push_macro/pop_macro` to protect macro names. `UNWRAP(...)
|
||||
- Metadata flags (`fieldMeta`): `sMD_ChangeOrig`, `sMD_ChangeNew`, `sMD_DeleteFinal`, `sMD_Create`, `sMD_Always`, `sMD_BaseTen` (decimal display), `sMD_PseudoAccount`, `sMD_NeedsAsset` (drives `STTakesAsset` association)
|
||||
- `IsSigning::no` excludes fields from signing hash (`sfTxnSignature`, `sfSigners`, `sfSignature`, etc.)
|
||||
- `isBinary()` ⇔ `fieldValue<256` (wire-representable); `isDiscardable()` ⇔ `fieldValue>256` (JSON-only, e.g., `sfHash`, `sfIndex`)
|
||||
- **Debug-only uniqueness check** during static init; release builds will silently mis-register on collision
|
||||
|
||||
## Wire Format Reference
|
||||
|
||||
@@ -64,6 +75,7 @@ Pattern uses `#pragma push_macro/pop_macro` to protect macro names. `UNWRAP(...)
|
||||
| MPT STAmount | 8 bytes header (bit63=0, bit61=1) + 192-bit MPTID |
|
||||
| IOU STAmount | bit63=1, bit62=sign, 8-bit (offset+97), 54-bit mantissa, +20B currency, +20B issuer |
|
||||
| AccountID | 20 bytes, VL-prefixed when standalone (`STAccount` mimics `STBlob` wire format) |
|
||||
| MPTID | 192 bits = 32-bit big-endian sequence ‖ 160-bit issuer |
|
||||
| STArray | elements between markers; ends with `STI_ARRAY,1` (`0xf1`) |
|
||||
| STObject | fields in canonical order; ends with `STI_OBJECT,1` (`0xe1`) |
|
||||
| VL prefix | 1 byte (0–192), 2 bytes (193–12480), 3 bytes (12481–918744); else `std::overflow_error` |
|
||||
@@ -71,6 +83,9 @@ Pattern uses `#pragma push_macro/pop_macro` to protect macro names. `UNWRAP(...)
|
||||
| LP token currency | byte0 = `0x03`; bytes 1-19 = `sha512Half(min(asset1,asset2), max(asset1,asset2))` low bits |
|
||||
| Order book quality | `(exponent+100) << 56 \| mantissa`; embedded in last 8 bytes of directory key (big-endian) so SHAMap order = price order |
|
||||
| NFTokenID (256-bit) | flags(2) + transferFee(2) + issuer(20) + cipheredTaxon(4) + serial(4); low 96 bits = page sort key |
|
||||
| LedgerHeader | 118 bytes fixed layout (seq, drops, parentHash, txHash, accountHash, parentClose/closeTime/closeFlags, closeTimeResolution) |
|
||||
| Payment channel claim | `HashPrefix::paymentChannelClaim` ‖ channelID(32) ‖ amount(8) |
|
||||
| Batch signing payload | `HashPrefix::batch` ‖ inner-tx hash list |
|
||||
|
||||
## Canonical Hashes
|
||||
|
||||
@@ -83,12 +98,13 @@ PRP → proposal MAN → manifest
|
||||
CLM → paymentChannelClaim BCH → batch
|
||||
```
|
||||
|
||||
All hashes use `sha512Half` (first 256 bits of SHA-512). `HashPrefix` constants are protocol-immutable.
|
||||
All hashes use `sha512Half` (first 256 bits of SHA-512). `HashPrefix` constants are protocol-immutable; the `make_hash_prefix(a,b,c)` constexpr packer in `HashPrefix.h` is the canonical way to declare new prefixes.
|
||||
|
||||
## STObject and STVar
|
||||
|
||||
- `STObject` stores `std::vector<detail::STVar>`; iterators expose `STBase const&` via transform iterator
|
||||
- `STVar` is type-erased with 72-byte inline buffer (small-object optimization); larger types heap-allocate
|
||||
- `STVar` is type-erased with 72-byte inline buffer (small-object optimization); `on_heap()` reports whether a value spilled; larger ST types heap-allocate
|
||||
- `STVar` is movable; moving an on-heap STVar steals the pointer, while inline ones must invoke each ST type's move ctor through the v-table
|
||||
- `copy()`/`move()` virtuals on every ST type delegate to `STBase::emplace()` for placement-new into `STVar`'s buffer
|
||||
- **Two modes:**
|
||||
- **Free** (`mType==nullptr`): linear field scan, accepts any field
|
||||
@@ -120,7 +136,7 @@ Proxies forbid removing `soeREQUIRED` or `soeDEFAULT` fields.
|
||||
## Common Bug Patterns
|
||||
|
||||
- Adding to `transactions.macro` without adding to `sfields.macro` → silent serialization failures
|
||||
- Forgetting to bump `numFeatures` after `XRPL_FEATURE` → out-of-bounds access in `FeatureBitset`
|
||||
- Forgetting to bump `numFeatures` after `XRPL_FEATURE` → static-init `LogicError` (registry overflow) caught at startup
|
||||
- Hand-built binary blobs in non-canonical field order → signature verification failures
|
||||
- Omitting `soeMPTSupported` on amount field → MPT payments silently rejected
|
||||
- Mutating `sfTransactionType` inside `STTx` assembler callback → `LogicError` (caught at startup)
|
||||
@@ -128,6 +144,10 @@ Proxies forbid removing `soeREQUIRED` or `soeDEFAULT` fields.
|
||||
- Storing `Currency` as `"XRP"` ISO code (`badCurrency()`) instead of zero → silently rejected; `to_currency()` legacy returns `badCurrency()` rather than failing
|
||||
- Forgetting to call `associateAsset(sle, asset)` near end of `doApply()` for vault/loan transactors → unrounded `STNumber` values
|
||||
- Returning `tec*` from `preflight()` → `NotTEC` type prevents this at compile time (would allow fee theft on unsigned tx)
|
||||
- `TxMeta::AffectedNodes` left unsorted before serialization → consensus-fork risk
|
||||
- Comparing `Issue` instances when one side is MPT-wrapped → `Issue::operator==` only compares currency+account; use `Asset` equality
|
||||
- Relying on debug-only `assert` inside `STObject::isFieldAllowed` to catch duplicate fields in release
|
||||
- Treating `numFeatures` as a length / iteration bound (it includes retired slots)
|
||||
|
||||
## Key Patterns
|
||||
|
||||
@@ -151,7 +171,7 @@ TER doApply(...); // can return any code including tec*
|
||||
|
||||
`TERSubset<Trait>` enforces this at compile time via `enable_if`. `TERtoInt(v)` is the authorized free-function conversion (member `explicit operator` would be too permissive in initializer contexts).
|
||||
|
||||
### Signing/Verifying
|
||||
### Signing / Verifying
|
||||
|
||||
```cpp
|
||||
sign(st, HashPrefix::txSign, KeyType::secp256k1, sk); // writes sfSignature
|
||||
@@ -164,6 +184,10 @@ finishMultiSigningData(signerAccountID, s); // append signer ID to shared paylo
|
||||
|
||||
`addWithoutSigningFields()` excludes signature fields from the signed payload — this is what breaks the circularity.
|
||||
|
||||
### Batch Signing
|
||||
|
||||
`HashPrefix::batch` + inner-tx hash list is signed by all inner accounts; outer tx carries the assembled `sfRawTransactions` array. `passesLocalChecks()` rejects nested batches.
|
||||
|
||||
### STNumber + STTakesAsset
|
||||
|
||||
```cpp
|
||||
@@ -172,13 +196,26 @@ finishMultiSigningData(signerAccountID, s); // append signer ID to shared paylo
|
||||
associateAsset(*sle, vaultAsset); // rounds all sMD_NeedsAsset fields, removes zero-defaults
|
||||
```
|
||||
|
||||
`STNumber` serializes a `Number` (signed mantissa+exponent) directly; rounding is asset-dependent and resolved by `associateAsset` walking fields flagged `sMD_NeedsAsset`.
|
||||
|
||||
### LP Token Currency Derivation
|
||||
|
||||
```cpp
|
||||
Currency lpc = ammLPTCurrency(asset1, asset2); // canonical std::minmax
|
||||
// byte 0 = 0x03 (LP marker), bytes 1..19 = low 152 bits of sha512Half(min, max)
|
||||
```
|
||||
|
||||
### Pseudo-Account Synthesis
|
||||
|
||||
Pseudo-accounts (AMM, Vault, LoanBroker) carry a 256-bit synthesized ID in fields flagged `sMD_PseudoAccount` (`sfAMMID`, `sfVaultID`, `sfLoanBrokerID`). These identify a stateless account address derived from the owner ledger entry.
|
||||
|
||||
## Critical Files
|
||||
|
||||
### Foundations
|
||||
- `include/xrpl/protocol/SField.h`, `src/libxrpl/protocol/SField.cpp` — field registry, X-macro expansion
|
||||
- `include/xrpl/protocol/Feature.h`, `src/libxrpl/protocol/Feature.cpp` — `numFeatures`, `FeatureBitset`, registration
|
||||
- `include/xrpl/protocol/Rules.h` — per-coroutine active amendment set; `isFeatureEnabled()` queries thread-local
|
||||
- `include/xrpl/protocol/HashPrefix.h` — protocol-immutable domain separators
|
||||
- `include/xrpl/protocol/SField.h`, `src/libxrpl/protocol/SField.cpp` — field registry, X-macro expansion, code packing
|
||||
- `include/xrpl/protocol/Feature.h`, `src/libxrpl/protocol/Feature.cpp` — `numFeatures` (ceiling!), `FeatureBitset`, `registerFeature` with compile-time name validation
|
||||
- `include/xrpl/protocol/Rules.h`, `src/libxrpl/protocol/Rules.cpp` — `Rules` snapshot of enabled amendments; `CurrentTransactionRules` is a `LocalValue<Rules const*>` (per-coroutine); `isFeatureEnabled()` queries thread-local
|
||||
- `include/xrpl/protocol/HashPrefix.h` — protocol-immutable domain separators; `make_hash_prefix` constexpr packer
|
||||
|
||||
### Macro Tables (single sources of truth)
|
||||
- `include/xrpl/protocol/detail/features.macro`
|
||||
@@ -188,62 +225,93 @@ associateAsset(*sle, vaultAsset); // rounds all sMD_NeedsAsset fields, removes
|
||||
- `include/xrpl/protocol/detail/permissions.macro`
|
||||
|
||||
### Type System Roots
|
||||
- `STBase.h/cpp` — polymorphic root, `emplace()` SOO helper, `JsonOptions`
|
||||
- `STObject.h/cpp` — heterogeneous container, proxy system, template enforcement
|
||||
- `STVar` (`detail/STVar.h`) — 72-byte inline variant, depth guard at 10
|
||||
- `SOTemplate.h/cpp` — schema with O(1) field index; move-only
|
||||
- `STBase.h/cpp` — polymorphic root; `emplace()` SOO helper; `JsonOptions`; `STExchange` traits glue
|
||||
- `STObject.h/cpp` — heterogeneous container, proxy system, template enforcement, debug uniqueness asserts
|
||||
- `STVar` (`detail/STVar.h`) — 72-byte inline variant; `on_heap()`; move steals pointer when heap-allocated; depth guard at 10
|
||||
- `SOTemplate.h/cpp` — schema with O(1) field index; move-only; carries `SOEStyle` + `SOETxMPTIssue`
|
||||
|
||||
### Format Registries
|
||||
- `TxFormats.h/cpp`, `LedgerFormats.h/cpp`, `InnerObjectFormats.h/cpp` — all inherit `KnownFormats<Key, Derived>` with `forward_list<Item>` (pointer-stable) + dual flat_maps
|
||||
- `LedgerEntry` rpcName vs name distinction enables `DepositPreauth` collision handling
|
||||
|
||||
### Amount/Asset Stack
|
||||
### Amount / Asset Stack
|
||||
- `Asset.h/cpp` — variant of Issue/MPTIssue; `visit()`, `equalTokens()`, `BadAsset` sentinel
|
||||
- `Issue.h/cpp`, `MPTIssue.h/cpp` — XRP/IOU and MPT identity
|
||||
- `STAmount.h/cpp` — unified serialized amount; `canMul`/`canAdd`/`canSubtract` safety checks; `mulRound`/`mulRoundStrict` (legacy vs precise rounding)
|
||||
- `Issue.h/cpp`, `MPTIssue.h/cpp` — XRP/IOU and MPT identity; **note** `Issue::operator==` ignores MPT-ness — always go through `Asset`
|
||||
- `STAmount.h/cpp` — unified serialized amount; `canMul`/`canAdd`/`canSubtract` safety checks; `mulRound`/`mulRoundStrict` (legacy vs precise rounding); `roundToScale`
|
||||
- `STNumber.h/cpp` — `Number`-typed field; pairs with `STTakesAsset` infrastructure
|
||||
- `STIssue.h/cpp`, `STCurrency.h/cpp` — asset-only fields
|
||||
- `STTakesAsset.h/cpp` — `associateAsset` walks `sMD_NeedsAsset` fields, rounds + strips zero-defaults
|
||||
- `IOUAmount.h/cpp`, `XRPAmount.h`, `MPTAmount.h/cpp` — lean representations
|
||||
- `Rate2.h` — `Rate` newtype with `parityRate = 1_000_000_000`; transfer-rate math
|
||||
- `Units.h` — phantom-typed `Drops`/`FeeLevel`
|
||||
- `Number` (in `xrpl/basics/`) — high-precision arithmetic; `MantissaRange::large` enabled by SingleAssetVault/LendingProtocol amendments
|
||||
- `AmountConversions.h` — typed coercions
|
||||
|
||||
### Cryptography
|
||||
- `PublicKey.h/cpp` — 33-byte unified format (0xED prefix for Ed25519); `ECDSACanonicality` enum (canonical vs fullyCanonical), libsecp256k1 normalization
|
||||
- `PublicKey.h/cpp` — 33-byte unified format (0xED prefix for Ed25519); `ECDSACanonicality` enum (canonical vs fullyCanonical); libsecp256k1 normalization
|
||||
- `SecretKey.h/cpp` — `secure_erase` in dtor; deleted `==`/`<<`; XRPL-specific secp256k1 derivation via `Generator`
|
||||
- `Seed.h/cpp` — 128-bit; `parseGenericSeed()` cascades hex→base58→RFC1751→passphrase, rejecting other key types first
|
||||
- `secp256k1.h` — libsecp256k1 context singleton
|
||||
- `digest.h` — `sha512Half`, `sha512_half_hasher_s` (secure erase variant)
|
||||
- `tokens.h/cpp` — Base58Check; fast path uses base 58^10 intermediate (10–15× speedup, gated on non-MSVC for `__int128`)
|
||||
- `tokens.h/cpp` (+ `b58_utils.h`, `token_errors.h`) — Base58Check; fast path uses base 58^10 intermediate (10–15× speedup, gated on non-MSVC for `__int128`); `TokenType` enum is protocol-stable
|
||||
|
||||
### Wire I/O
|
||||
- `Serializer.h/cpp` — accumulator; `addVL`, `addFieldID`, big-endian integers, `getSHA512Half()`
|
||||
- `SerialIter` — non-owning forward cursor over a byte buffer; throws on underrun
|
||||
- `Sign.h/cpp` — `sign`/`verify` with HashPrefix prepended to `addWithoutSigningFields()` output
|
||||
- `Sign.h/cpp` — `sign`/`verify` with HashPrefix prepended to `addWithoutSigningFields()` output; `signingForID` helper for arbitrary payload bytes
|
||||
- `serialize.h` — top-level convenience helpers
|
||||
- `messages.h` — protobuf message tag constants
|
||||
|
||||
### Higher-Level Objects
|
||||
- `STTx.h/cpp` — caches `tid_` and `tx_type_`; `passesLocalChecks` (memos, pseudo-tx, MPT support, batch nesting); `sterilize()` round-trip
|
||||
- `STLedgerEntry.h/cpp` (alias `SLE`) — typed ledger object; `thread()` updates `sfPreviousTxnID`; `isThreadedType()` gated by `fixPreviousTxnID`
|
||||
- `STValidation.h/cpp` — lazy `valid_` cache; `mTrusted` separate from validity; `lookupNodeID` callback decouples manifest system
|
||||
- `STArray.h/cpp`, `STVector256.h/cpp`, `STBitString.h/cpp`, `STInteger.h/cpp`, `STBlob.h/cpp`, `STAccount.h/cpp`, `STPathSet.h/cpp`, `STXChainBridge.h/cpp`
|
||||
|
||||
### Transaction Meta
|
||||
- `TxMeta.h/cpp` — `AffectedNodes` (sorted by index!), `DeliveredAmount`; serialization order is consensus-critical
|
||||
- `LedgerHeader.h/cpp` — 118-byte fixed serialization; close-time-resolution fudging
|
||||
|
||||
### Indexes and Keys
|
||||
- `Indexes.h/cpp` — `keylet::*` factories with `LedgerNameSpace` tagged hashing; `keylet::quality()` embeds 64-bit quality in last 8 bytes (big-endian)
|
||||
- `Keylet.h/cpp` — type-tagged `(uint256, LedgerEntryType)`; `ltANY` wildcard, `ltCHILD` rejects directories
|
||||
- NFT pages: composite keys (high 160 = owner AccountID, low 96 = token range); `nft::pageMask` is the boundary
|
||||
- `Protocol.h` — protocol-wide constants (`FLAG_LEDGER_INTERVAL`, etc.)
|
||||
- `nftPageMask.h`, `nft.h` — NFT page boundary (low 96 bits); composite keys (high 160 = owner AccountID)
|
||||
- `NFTokenID.h/cpp` — flags(2)+fee(2)+issuer(20)+cipheredTaxon(4)+serial(4); LCG `384160001 * seq + 2459` ciphers taxon
|
||||
- `NFTokenOfferID.h/cpp`, `NFTSyntheticSerializer.h/cpp` — derived/synthetic NFT entries
|
||||
- `Book.h` — `(in_asset, out_asset)` order-book identity
|
||||
- `SeqProxy.h/cpp` — sequence vs ticket abstraction
|
||||
|
||||
### Validation Helpers (return NotTEC, preflight-time)
|
||||
- `AMMCore.h/cpp` — `invalidAMMAsset`, `invalidAMMAssetPair`, `invalidAMMAmount`; `ammLPTCurrency()` uses canonical `std::minmax`
|
||||
- `Permissions.h/cpp` — singleton; `isDelegable()` checks granular vs transaction-level (`<UINT16_MAX` boundary), amendment, delegable flag
|
||||
|
||||
### RPC/JSON Boundary
|
||||
### RPC / JSON Boundary
|
||||
- `STParsedJSON.h/cpp` — depth cap 64; field-path-qualified errors via `make_name`; recognizes `"Payment"`, `"tesSUCCESS"`, etc.
|
||||
- `ErrorCodes.h/cpp` — append-only enum; `sortedErrorInfos` validated at compile time
|
||||
- `MultiApiJson.h` — per-API-version `Json::Value` array; composes with `forAllApiVersions` from `ApiVersion.h`
|
||||
- `ErrorCodes.h/cpp` — append-only enum; `sortedErrorInfos` validated at compile time; `warning_code_i` distinct from `error_code_i`
|
||||
- `RPCErr.h/cpp` — `RPC::Status`/`make_error` helpers
|
||||
- `MultiApiJson.h` — per-API-version `Json::Value` array indexed `[version - RPC::apiMinimumVersion]`; composes with `forAllApiVersions` from `ApiVersion.h`; preserves wire compatibility across versions
|
||||
- `jss.h` — every JSON key as `Json::StaticString` via `JSS(name)` macro; PascalCase = protocol fields, snake_case = RPC
|
||||
- `json_get_or_throw.h` — `getOrThrow<T>(jv, name)` specializations enforce presence + type; standard idiom for parsing untrusted JSON
|
||||
- `st.h` — convenience aggregate header for all ST types
|
||||
|
||||
### Specialized Types
|
||||
- `STIssue`, `STAccount` (160-bit, VL-encoded), `STBitString<Bits>`, `STInteger<T>`, `STBlob`, `STArray`, `STVector256`, `STCurrency`, `STPathSet`, `STXChainBridge`, `STNumber` (asset-contextual)
|
||||
- `Quality.h/cpp` — inverted encoding (lower uint64 = higher quality); `ceil_in`/`ceil_out` proportional scaling; `_strict` variants honor Number rounding mode
|
||||
- `QualityFunction.h/cpp` — linear `q(out)=m*out+b`; AMMTag (slope from pool) vs CLOBLikeTag (m=0); `combine()` for multi-step strands
|
||||
- `XChainAttestations.h/cpp` — Attestations:: namespace (full, with signature) vs xrpl:: (stored); `match()` returns three-state `AttestationMatch`
|
||||
- `XChainAttestations.h/cpp` — `Attestations::` namespace (full, with signature) vs `xrpl::` (stored); `match()` returns three-state `AttestationMatch`
|
||||
|
||||
### Pseudo-Account Fields (sMD_PseudoAccount)
|
||||
### Pseudo-Account Fields (`sMD_PseudoAccount`)
|
||||
- `sfAMMID`, `sfVaultID`, `sfLoanBrokerID` — 256-bit hash representing a synthesized account address
|
||||
|
||||
### Misc / System
|
||||
- `BuildInfo.h/cpp` — version string, `getVersionString()` consumed by manifest/handshake
|
||||
- `SystemParameters.h` — drops-per-XRP, `INITIAL_XRP`, ledger-related constants; validated by `static_assert`
|
||||
- `UintTypes.h` — `uint256`/`uint160`/`uint128` aliases and tagged variants (`Currency`, `NodeID`, etc.)
|
||||
- `TER.h/cpp` — error code enum families + `TERSubset`
|
||||
- `TxFlags.h` — X-macro driven flag tables (`tf*`); see TxFlags Architecture below
|
||||
- `TxFormats.h/cpp` — transaction-type → field schema
|
||||
|
||||
## Numeric Encoding Reference
|
||||
|
||||
```
|
||||
@@ -253,6 +321,7 @@ XRP max: cMaxNativeN = 10^17 drops (100 billion XRP)
|
||||
MPT max: maxMPTokenAmount = INT64_MAX = 0x7FFFFFFFFFFFFFFF
|
||||
Transfer rate: Rate{value} where value/1_000_000_000 = 1.0 (parityRate = 1:1)
|
||||
NFT transfer fee: uint16 basis points (0–50000), convert via nft::transferFeeAsRate (×10000)
|
||||
AMM auction fee: basis points; trading fee in tenths of basis points (10000 = 1%)
|
||||
```
|
||||
|
||||
## Protocol-Stable Constants (NEVER CHANGE)
|
||||
@@ -262,10 +331,26 @@ NFT transfer fee: uint16 basis points (0–50000), convert via nft::transferFee
|
||||
- `SerializedTypeID` and `SField` codes (in serialized fields)
|
||||
- `LedgerNameSpace` discriminator characters (in keylet derivation)
|
||||
- `HashPrefix` enum values (in signature/hash domain separation)
|
||||
- `error_code_i` numeric values (clients depend on them; append-only)
|
||||
- `error_code_i` and `warning_code_i` numeric values (clients depend on them; append-only)
|
||||
- `TECcodes` (and other `TER` family numeric values) — recorded in transaction metadata
|
||||
- `TokenType` (Base58Check prefix bytes for accounts/seeds/nodes)
|
||||
- LP token currency prefix byte (`0x03`)
|
||||
- Universal transaction flags (`tfFullyCanonicalSig`, `tfInnerBatchTxn`)
|
||||
- `FLAG_LEDGER_INTERVAL = 256` (drives consensus timing)
|
||||
- `INITIAL_XRP = 100B × 10^6 drops` (validated by `static_assert` against `Number::maxRep`)
|
||||
- NFT taxon LCG constants (`384160001 * seq + 2459`)
|
||||
- All flag bit values (`tf*`, `lsf*`, `asf*`)
|
||||
|
||||
Changing any requires an amendment with explicit detection logic for old/new behavior.
|
||||
|
||||
## TxFlags Architecture
|
||||
|
||||
`TxFlags.h` is itself X-macro driven. Per-transaction flag groups are declared so that:
|
||||
|
||||
- Each group has `tf*` named bit constants
|
||||
- `tfUniversalMask` is the union of universal flags (`tfFullyCanonicalSig`, `tfInnerBatchTxn`)
|
||||
- Per-transaction `tf*Mask` constants are auto-computed via `MASK_ADJ` so that mask matches the declared flags exactly — adding a flag automatically updates the mask
|
||||
- `TF_FLAG2` marks flags whose meaning was changed by an amendment; old/new bits coexist with disjoint enable conditions
|
||||
- Inner-batch flag `tfInnerBatchTxn` is special: marks a tx as a member of a batch (skipped by ordinary preflight signature checks)
|
||||
|
||||
Pattern: when adding a new flag, define it in `TxFlags.h` in the appropriate group; do NOT manually adjust the mask — the macro derives it.
|
||||
|
||||
Reference in New Issue
Block a user