Compare commits

..

245 Commits

Author SHA1 Message Date
Ed Hennis
dd4eca22b7 Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-17 15:01:23 -04:00
Ed Hennis
1116dbfd0e Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-16 18:56:01 -04:00
Ed Hennis
258d5e1f1f clang-tidy: nodiscard rendezvous, headers, const correctness, {}s 2026-06-16 18:47:40 -04:00
Ed Hennis
4270367efc Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-15 21:32:49 -04:00
Ed Hennis
fad34e852d More AI review feedback:
- Move a log message into the same block as the thing it's logging.
- Change default recovery time from 1s to 2s. This may need some more
  tuning.
- Rename the online_delete test config helper function to onlineDelete.
- Copy some class values while under lock, even though they can't
  change, as defense in depth.
- Use RAII scope unlock in healthWait().
2026-06-15 21:28:23 -04:00
Ed Hennis
f5b4ac358f AI review feedback
- Remove extraneous function declaration.
- Add a timeout to SHAMapStore_test::testLedgerGaps so it won't hang in
  case of regression.
- Add an optional timeout parameter to SHAMapStore::rendezvous, and
  return a success flag.
  - Use the timeout in SHAMapStore_test::testLedgerGaps to show lack of
    progress in the Store without an arbitrary sleep delay.
- Refactor LedgerMaster::missingFromCompleteLedgerRange to use RangeSet
  functions instead of a naive iteration.
- If lastGoodValidatedLedger_ is default (0) in healthWait(), then act
  as if no ledgers are missing.
2026-06-15 20:13:40 -04:00
Ed Hennis
875bcc530e Correct a couple of variable names 2026-06-15 16:47:29 -04:00
Ed Hennis
e75f5b101b Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-15 15:28:44 -04:00
Ed Hennis
95f74d61b2 Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-15 12:11:54 -04:00
Ed Hennis
4e12b787be Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop: (22 commits)
  test: Add null check unit test for `Oracle::aggregatePrice` (7306)
  ci: Patch conan recipe for Nix to be able to use on macOS (7532)
  ci: Run sanitizers on release builds too (7527)
  fix: Correct hybrid offer deletion on credential expiry (6843)
  ci: Make sanitizer flags lists in the profile, not a string (7449)
  ci: Make configurations launch on certain event types (7447)
  fix: Add [[maybe_unused]] to fix320Enabled for assert=OFF builds (7446)
  ci: Add `gh` and `file` to nix packages (7444)
  fix: Disable transaction invariants (7409)
  perf: Dispatch "hasInvalidAmount()" on type tag instead of dynamic_cast (7402)
  refactor: Retire fixUniversalNumber amendment (5962)
  test: Do not create data directory for memory databases (7323)
  ci: Launch upload-conan-deps on profile change (7442)
  fix: Fix Number comparison operator (7406)
  feat: Use C++ 23 standard (7431)
  refactor: Introduce XRPL_ASSERT_IF for amendment-gated assertions (7378)
  refactor: Change config section and key string literals into constants (7095)
  refactor: Use `std::move` and `std::string_view` where possible (7424)
  refactor: Use const function arguments where possible (7423)
  ci: Use XRPLF/actions build-multiarch-image workflow (7428)
  ...
2026-06-11 21:34:19 -04:00
Ed Hennis
8b6c80027b Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-05 18:48:40 -04:00
Ed Hennis
ace73678cf Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-04 13:32:19 -04:00
Ed Hennis
12bbba00b0 Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-04 10:08:25 -04:00
Ed Hennis
2affb66f50 Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-02 11:58:29 -04:00
Ed Hennis
d09e785c39 Merge branch 'develop' into ximinez/online-delete-gaps 2026-06-01 14:24:26 -04:00
Ed Hennis
97fdf310f9 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-28 23:47:49 -04:00
Ed Hennis
d3ce76825c Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-27 17:00:52 -04:00
Ed Hennis
1e7e274727 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-27 15:18:10 -04:00
Ed Hennis
b272d71cc7 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-27 12:06:52 -04:00
Ed Hennis
21268d9c36 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-26 19:20:55 -04:00
Ed Hennis
932e22df7d Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-26 16:56:36 -04:00
Ed Hennis
652a7cd225 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-26 16:01:32 -04:00
Ed Hennis
4a224dbfa4 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-22 17:56:07 -04:00
Ed Hennis
d2a5981f87 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-21 14:25:41 -04:00
Ed Hennis
a977836630 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-21 10:09:42 -04:00
Ed Hennis
bf075200bb Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop:
  docs: Add --parallel flag to cmake build commands in BUILD.md (7302)
  fix: Fix wrong hybrid offer orderbook placement and update `LedgerStateFix` to amend `ExchangeRate` meta (7087)
  style: More clang-tidy identifier renaming (7290)
  fix: Update pDEX invariant firing under a valid offer deletion (7118)
  fix: Fix multisign and signfor to check for delegate (7064)
  refactor: Fix `sfGeneric` and `sfInvalid` field names (7300)
  docs: Fix some comments to improve readability (7122)
  feat: Propagate underlying MPT flags to vault shares (7077)
2026-05-21 11:59:26 +01:00
Ed Hennis
ee49e76a16 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-19 16:53:27 -04:00
Ed Hennis
c1318990f3 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-19 10:15:02 -04:00
Ed Hennis
69128294f8 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-19 05:15:40 -04:00
Ed Hennis
149e884ebc Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-15 21:32:03 -04:00
Ed Hennis
d27353225c Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop:
  release: Set version to 3.3.0-b0 (7280)
  refactor: Rename static constants (7120)
  refactor: Use `isFlag` where possible instead of bitwise math (7278)
  ci: Update XRPLF/actions (7281)
2026-05-15 15:47:01 -04:00
Ed Hennis
13a0f77eb5 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-14 20:39:04 -04:00
Ed Hennis
549c093398 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-13 20:16:12 -04:00
Ed Hennis
fb66ca7a2e Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-13 12:03:27 -04:00
Ed Hennis
e2af73b0a0 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-12 19:18:39 -04:00
Ed Hennis
14c3c9a256 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-12 16:26:02 -04:00
Ed Hennis
4b6851a287 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-12 15:59:13 -04:00
Ed Hennis
d182673d24 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-11 13:33:56 -04:00
Ed Hennis
bea609d805 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-07 18:09:54 -04:00
Ed Hennis
56eeb20bc8 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-07 14:18:14 -04:00
Ed Hennis
e293a3d918 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-07 13:28:18 -04:00
Ed Hennis
3dff580d39 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-06 22:34:11 -04:00
Ed Hennis
487f5a0fd3 clang-tidy fix: lock_guard -> scoped_lock 2026-05-06 18:44:16 -04:00
Ed Hennis
d8134f98e9 Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-06 14:17:47 -04:00
Ed Hennis
450a623d4b Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-05 16:46:26 -04:00
Ed Hennis
9e517be4ce Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-05 10:50:18 -04:00
Ed Hennis
b8370438fb Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop:
  ci: Rewrite clang-tidy workflow(s) in a reusable manner (7062)
  chore: Ignore identifier-naming update in git blame (7066)
  refactor: Enable clang-tidy `readability-identifier-naming` check (6571)
2026-05-04 21:36:05 -04:00
Ed Hennis
ecb5604d3b Merge branch 'develop' into ximinez/online-delete-gaps 2026-05-01 12:54:31 -04:00
Ed Hennis
c4527e7b0f Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop:
  fix: Gate -mcmodel flags to x86_64 in sanitizer builds (7049)
  fix: Prevents overwriting a bool value in an invariant (6609)
  fix: Address code review comments regarding `boost::coroutine2` (6977)
  refactor: Apply various minor improvements and corrections (7045)
  fix: Store `Delegate` object in delegating and authorized account directories for proper deletion (6681)
  ci: Use print-env from XRPLF/actions (7052)
  fix: Make assorted RPC fixes (6529)
  chore: Enable clang-tidy v21 new checks (7031)
2026-04-30 14:30:36 -04:00
Ed Hennis
778e2b3ce8 Fix clang-tidy: missing include 2026-04-28 15:44:14 -05:00
Ed Hennis
295d03aec8 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-28 16:23:08 -04:00
Ed Hennis
9262c2e624 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-25 14:41:57 -04:00
Ed Hennis
b385a41aa5 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-23 15:55:43 -04:00
Ed Hennis
4eb9726097 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-22 23:39:00 -04:00
Ed Hennis
28a38ef1cc Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-22 14:48:51 -04:00
Ed Hennis
39f9380b2b Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-22 11:36:03 -04:00
Ed Hennis
239fcaceb4 clang-tidy fix: Use std::to_string for std::uint32_t 2026-04-21 19:30:05 -04:00
Ed Hennis
8ab86f009e Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-21 18:47:53 -04:00
Ed Hennis
d4b58a74f4 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-21 14:37:52 -04:00
Ed Hennis
ff900591ae Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-21 10:40:45 -04:00
Ed Hennis
a5b7471af6 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-20 17:49:20 -04:00
Ed Hennis
e0734986dd Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-20 15:44:37 -04:00
Ed Hennis
8440f479e5 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-20 11:37:54 -04:00
Ed Hennis
ac390622e0 Fix clang-tidy issues 2026-04-17 17:54:37 -04:00
Ed Hennis
5d807f0d6d Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop:
  chore: Enable clang-tidy include cleaner (6947)
  fix: Change AMMClawback return code to tecNO_PERMISSION (6946)
  ci: [DEPENDABOT] bump actions/upload-pages-artifact from 4.0.0 to 5.0.0 (6927)
  ci: [DEPENDABOT] bump actions/upload-artifact from 7.0.0 to 7.0.1 (6928)
  chore: Enable clang-tidy readability checks (6930)
2026-04-17 15:23:33 -04:00
Ed Hennis
b93294f26b Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-16 13:44:23 -04:00
Ed Hennis
ef95ace0f9 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-15 19:06:15 -04:00
Ed Hennis
fc58bf6edf Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-15 14:28:41 -04:00
Ed Hennis
348555d5ba Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-13 20:28:29 -04:00
Ed Hennis
302802e42d Fix clang-tidy issues 2026-04-13 20:23:23 -04:00
Ed Hennis
5d881f87a3 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-10 12:12:27 -04:00
Ed Hennis
a8a8035b32 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-10 09:10:39 -04:00
Ed Hennis
45af14231f Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-09 11:39:55 -04:00
Ed Hennis
380bf274d0 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-08 16:56:56 -04:00
Ed Hennis
460ec5eeea Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-08 15:12:46 -04:00
Ed Hennis
14be8ca4ea Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-07 16:47:32 -04:00
Ed Hennis
948264d44c Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-06 19:06:51 -04:00
Ed Hennis
9c816b2043 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-06 18:33:20 -04:00
Ed Hennis
f68402acd1 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-06 16:52:50 -04:00
Ed Hennis
5358e25eaa Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-06 13:24:22 -04:00
Ed Hennis
a34d1e5537 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-01 17:26:14 -04:00
Ed Hennis
78a122943d Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-01 13:39:05 -04:00
Ed Hennis
06135e7203 Merge branch 'develop' into ximinez/online-delete-gaps 2026-04-01 11:45:32 -04:00
Ed Hennis
f20425fa4f Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-30 21:29:37 -04:00
Ed Hennis
3e94546acc Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-12 15:05:04 -04:00
Ed Hennis
3b088ed0dc Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-10 12:46:18 -04:00
Ed Hennis
26182ed52e Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-06 13:04:00 -04:00
Ed Hennis
de2a3e10f5 Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-05 21:34:29 -04:00
Ed Hennis
e17f8554fc Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-04 17:11:27 -04:00
Ed Hennis
386a7192ba Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-03 20:46:41 -04:00
Ed Hennis
12cc6e424d Merge branch 'develop' into ximinez/online-delete-gaps 2026-03-03 15:54:32 -04:00
Ed Hennis
c9deecf1b7 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-24 17:34:32 -04:00
Ed Hennis
ddd1b49f38 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-24 16:45:55 -04:00
Ed Hennis
c1b2a24005 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-20 18:49:39 -04:00
Ed Hennis
86d88eca31 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-20 18:25:58 -04:00
Ed Hennis
ef09eaea00 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-20 17:31:41 -04:00
Ed Hennis
c504cfb291 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-20 17:21:03 -04:00
Ed Hennis
0c217dfa2b Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-20 15:14:23 -04:00
Ed Hennis
b0198d2566 Merge remote-tracking branch 'upstream/develop' into ximinez/online-delete-gaps
* upstream/develop:
  ci: Add dependabot config (6379)
  Fix tautological assertion (6393)
  chore: Apply clang-format width 100 (6387)
2026-02-20 12:20:07 -05:00
Ed Hennis
7eee8ca802 Update formatting 2026-02-20 12:15:30 -05:00
Ed Hennis
2a079a0154 Merge commit '25cca465538a56cce501477f9e5e2c1c7ea2d84c' into ximinez/online-delete-gaps
* commit '25cca465538a56cce501477f9e5e2c1c7ea2d84c':
  chore: Set clang-format width to 100 in config file (6387)
2026-02-20 12:12:56 -05:00
Ed Hennis
40989c1178 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-19 16:21:11 -05:00
Ed Hennis
addc831eb3 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-18 18:06:20 -04:00
Ed Hennis
b4efc6d116 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-04 16:29:49 -04:00
Ed Hennis
125d075d6e Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-04 14:16:24 -04:00
Ed Hennis
370a775479 Merge branch 'develop' into ximinez/online-delete-gaps 2026-02-03 16:07:47 -04:00
Ed Hennis
1a2ee706eb Fix formatting 2026-01-28 19:43:23 -05:00
Ed Hennis
2a981357ba Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-28 18:40:24 -04:00
Ed Hennis
1ae475e724 Merge commit '5f638f55536def0d88b970d1018a465a238e55f4' into ximinez/online-delete-gaps
* commit '5f638f55536def0d88b970d1018a465a238e55f4':
  chore: Set ColumnLimit to 120 in clang-format (6288)
2026-01-28 17:38:29 -05:00
Ed Hennis
a3e9401fbc Merge commit '92046785d1fea5f9efe5a770d636792ea6cab78b' into ximinez/online-delete-gaps
* commit '92046785d1fea5f9efe5a770d636792ea6cab78b':
  test: Fix the `xrpl.net` unit test using async read (6241)
  ci: Upload Conan recipes for develop, release candidates, and releases (6286)
  fix: Stop embedded tests from hanging on ARM by using `atomic_flag` (6248)
  fix:  Remove DEFAULT fields that change to the default in associateAsset (6259) (6273)
  refactor: Update Boost to 1.90 (6280)
  refactor: clean up uses of `std::source_location` (6272)
  ci: Pass missing sanitizers input to actions (6266)
  ci: Properly propagate Conan credentials (6265)
  ci: Explicitly set version when exporting the Conan recipe (6264)
  ci: Use plus instead of hyphen for Conan recipe version suffix (6261)
  chore: Detect uninitialized variables in CMake files (6247)
  ci: Run on-trigger and on-pr when generate-version is modified (6257)
  refactor: Enforce 15-char limit and simplify labels for thread naming (6212)
  docs: Update Ripple Bug Bounty public key (6258)
  ci: Add missing commit hash to Conan recipe version (6256)
  fix: Include `<functional>` header in `Number.h` (6254)
  ci: Upload Conan recipe for merges into develop and commits to release (6235)
  Limit reply size on `TMGetObjectByHash` queries (6110)
  ci: remove 'master' branch as a trigger (6234)
  Improve ledger_entry lookups for fee, amendments, NUNL, and hashes (5644)
2026-01-28 17:38:13 -05:00
Ed Hennis
9091469f9e Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-15 13:03:03 -04:00
Ed Hennis
17fa54f1f9 Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-15 12:05:41 -04:00
Ed Hennis
8fb5347c2d Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-14 19:20:11 -04:00
Ed Hennis
6739bf998f Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-13 18:04:18 -04:00
Ed Hennis
6eea38ba67 Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-13 16:15:44 -04:00
Ed Hennis
e9cf88b359 Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-13 15:01:56 -04:00
Ed Hennis
645b203476 Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-12 21:07:23 -04:00
Ed Hennis
be2aff1f4c Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-12 14:51:34 -04:00
Ed Hennis
56ed237e82 Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-11 00:48:27 -04:00
Ed Hennis
fd7b0fd135 Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-08 17:05:28 -04:00
Ed Hennis
e700994891 Merge branch 'develop' into ximinez/online-delete-gaps 2026-01-08 13:03:41 -04:00
Ed Hennis
c76f7029ac Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop:
  test: add more tests for `ledger_entry` RPC (5858)
  refactor: Rename `rippled.cfg` to `xrpld.cfg` (6098)
  Revert "chore: Pin ruamel.yaml<0.19 in pre-commit-hooks (6166)" (6167)
  chore: Pin ruamel.yaml<0.19 in pre-commit-hooks (6166)
  fix: Remove cryptographic libs from libxrpl Conan package (6163)
2026-01-06 11:27:23 -05:00
Ed Hennis
d535c5fb2a Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-22 17:39:25 -05:00
Ed Hennis
54f860463e Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-18 18:15:15 -05:00
Ed Hennis
950434b8ff Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-17 12:12:39 -05:00
Ed Hennis
ee365e876d Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-12 20:34:29 -05:00
Ed Hennis
c6c59834b9 Update View info() to header() 2025-12-12 15:35:07 -05:00
Ed Hennis
63b47914b8 Merge remote-tracking branch 'XRPLF/develop' into ximinez/online-delete-gaps
* XRPLF/develop:
  refactor: Rename `ripple` namespace to `xrpl` (5982)
  refactor: Move JobQueue and related classes into xrpl.core module (6121)
  refactor: Rename `rippled` binary to `xrpld` (5983)
2025-12-11 15:25:43 -05:00
Ed Hennis
9e02e5be2e Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-10 18:55:14 -05:00
Ed Hennis
093cd70fa1 Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-05 21:12:43 -05:00
Ed Hennis
376d65a483 Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-02 17:21:39 -05:00
Ed Hennis
a0d9a2458e Merge branch 'develop' into ximinez/online-delete-gaps 2025-12-01 14:40:15 -05:00
Ed Hennis
456f639cf7 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-28 15:46:19 -05:00
Ed Hennis
2c559ec2f3 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-27 01:48:33 -05:00
Ed Hennis
619c81f463 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-26 00:24:50 -05:00
Ed Hennis
f1490df960 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-25 14:54:38 -05:00
Ed Hennis
7bdf74de98 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-24 21:48:42 -05:00
Ed Hennis
1743d6fb98 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-24 21:29:52 -05:00
Ed Hennis
ca7a5bb926 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-21 12:47:28 -05:00
Ed Hennis
ce8b1a3f1e Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-18 22:39:06 -05:00
Ed Hennis
486fa75a10 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-15 03:08:18 -05:00
Ed Hennis
f8d68cd3d3 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-13 12:18:08 -05:00
Ed Hennis
ef7a3f5606 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-12 13:59:45 -05:00
Ed Hennis
4f84ed7490 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-10 19:52:42 -05:00
Ed Hennis
d534103131 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-10 15:34:55 -05:00
Ed Hennis
82dff3c2ce Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-08 22:49:26 -05:00
Ed Hennis
30d73eb5ba Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-06 23:50:23 -05:00
Ed Hennis
1b2754bac2 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-05 22:23:07 -05:00
Ed Hennis
cf80cafc75 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-04 18:02:30 -05:00
Ed Hennis
b8897d51de Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-03 18:03:56 -05:00
Ed Hennis
3ff25eeb65 Merge branch 'develop' into ximinez/online-delete-gaps 2025-11-03 12:41:48 -05:00
Ed Hennis
2bbfc4e786 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-31 13:51:15 -04:00
Ed Hennis
2b1eb052e6 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-31 12:51:24 -04:00
Ed Hennis
360e214e54 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-29 14:23:10 -04:00
Ed Hennis
2618afed94 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-29 13:42:21 -04:00
Ed Hennis
698ba2c788 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-29 12:54:16 -04:00
Ed Hennis
b614e99588 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-28 17:38:21 -04:00
Ed Hennis
fe8e4af2fa Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-26 19:12:33 -04:00
Ed Hennis
0a897f1528 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-23 13:24:25 -04:00
Ed Hennis
cf8a3f5779 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-22 11:38:49 -04:00
Ed Hennis
db39a39868 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-21 22:20:04 -04:00
Ed Hennis
37a03d28c2 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-17 18:21:36 -04:00
Ed Hennis
19d275425a Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-16 13:12:08 -04:00
Ed Hennis
88e9045602 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-16 10:48:43 -04:00
Ed Hennis
5adbc536b6 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-10 13:01:32 -04:00
Ed Hennis
e27af94ba9 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-09 15:14:47 -04:00
Ed Hennis
43fe1e7e9c Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-08 14:21:34 -04:00
Ed Hennis
f456a858c8 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-02 11:03:07 -04:00
Ed Hennis
084c3aa88e Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-01 18:10:57 -04:00
Ed Hennis
34f9b63921 Merge branch 'develop' into ximinez/online-delete-gaps 2025-10-01 13:14:22 -04:00
Ed Hennis
bd3de79817 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-30 22:28:53 -04:00
Ed Hennis
304eee2259 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-29 18:34:43 -04:00
Ed Hennis
9e729b7f59 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-29 17:37:16 -04:00
Ed Hennis
dd141468c4 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-29 13:32:03 -04:00
Ed Hennis
933147c21f Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-26 19:25:58 -04:00
Ed Hennis
9201a4f591 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-26 13:41:27 -04:00
Ed Hennis
5adb1e9b8b Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-26 12:09:09 -04:00
Ed Hennis
4df84d7988 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-25 13:27:09 -04:00
Bart
cd87c0968b Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-24 09:35:19 +02:00
Ed Hennis
8a8e7c90bf Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-20 15:44:32 -04:00
Ed Hennis
e806069065 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-18 14:08:23 -04:00
Ed Hennis
ce948cbec0 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-18 12:26:34 -04:00
Ed Hennis
6ed34b3294 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-18 11:54:30 -04:00
Ed Hennis
7161a235ca Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-17 10:49:03 -04:00
Ed Hennis
71463810de Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-16 10:46:37 -04:00
Ed Hennis
e997219a85 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-15 11:13:28 -04:00
Ed Hennis
895cc13fa6 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-11 10:33:14 -04:00
Ed Hennis
8d3c3ca29a Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-10 18:53:24 -04:00
Ed Hennis
9829553807 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-09 17:14:20 -04:00
Ed Hennis
e551f9731a Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-08 11:41:46 -04:00
Ed Hennis
fd827bf58b Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-05 17:44:06 -04:00
Ed Hennis
5a3baba34d Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-04 20:24:55 -04:00
Ed Hennis
c78f5b160f Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-04 16:43:48 -04:00
Ed Hennis
485f78761a Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-04 12:26:55 -04:00
Ed Hennis
23cd2f7b21 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-04 10:14:10 -04:00
Ed Hennis
5753266c43 Merge branch 'develop' into ximinez/online-delete-gaps 2025-09-03 14:04:01 -04:00
Ed Hennis
4722d2607d Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-29 15:52:53 -04:00
Ed Hennis
85b5b4f855 Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-29 10:42:50 -04:00
Ed Hennis
a16f492f0f Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-28 18:17:26 -04:00
Ed Hennis
3633dc632c Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-27 11:14:59 -04:00
Ed Hennis
b3b30c3a86 Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-25 14:10:52 -04:00
Ed Hennis
c78a7684f4 Remove trailing space 2025-08-25 14:09:57 -04:00
Ed Hennis
cf83d92630 Merge remote-tracking branch 'upstream/develop' into ximinez/online-delete-gaps
* upstream/develop:
  chore: Remove codecov token check to support tokenless uploads on forks (5722)
  Set version to 2.6.0-rc3
  Revert "perf: Move mutex to the partition level (5486)"
  chore: Update clang-format and prettier with pre-commit (5709)
  fix(test): handle null metadata for unvalidated tx in Env::meta (5715)
  chore: Workaround for CI build errors on arm64 (5717)
  chore: Fix file formatting (5718)
  fix: Skip notify-clio when running in a fork, reorder config fields (5712)
  chore: Reverts formatting changes to external files, adds formatting changes to proto files (5711)
2025-08-25 14:05:49 -04:00
Ed Hennis
a56b1274d8 Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-21 11:38:54 -04:00
Ed Hennis
ae4bdd0492 Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-19 16:05:17 -04:00
Ed Hennis
e90102dd3b Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-18 12:15:38 -04:00
Ed Hennis
71f0e8db3d Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-08 18:23:24 -04:00
Ed Hennis
638929373a Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-08 11:10:20 -04:00
Ed Hennis
8440654377 Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-06 21:03:00 -04:00
Ed Hennis
9fa66c4741 Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-05 21:16:44 -04:00
Ed Hennis
38a9235145 Merge branch 'develop' into ximinez/online-delete-gaps 2025-08-04 13:04:52 -04:00
Ed Hennis
c7a3cc9108 Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-29 20:33:29 -04:00
Ed Hennis
248337908d Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-29 11:54:16 -04:00
Ed Hennis
3d003619fd Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-28 20:57:13 -04:00
Ed Hennis
f163dca12c Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-24 15:50:09 -04:00
Ed Hennis
6e0ce458e5 Revert "TEMP: Change some logging to fatal to diagnose CI failures"
This reverts commit 69cf18158b.
2025-07-22 19:41:02 -04:00
Ed Hennis
5fae8480f1 Revert "TEMP: Add logging to SHAMapStore test"
This reverts commit fe7d0798a7.
2025-07-22 19:40:58 -04:00
Ed Hennis
e6587d374a fixup! Tweak SHAMapStore test timing more 2025-07-22 19:39:58 -04:00
Ed Hennis
376cc404e0 Tweak SHAMapStore test timing more 2025-07-22 18:44:07 -04:00
Ed Hennis
9898ca638f Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-22 14:09:33 -04:00
Ed Hennis
34b46d8f7c Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-21 18:20:41 -04:00
Ed Hennis
fe7d0798a7 TEMP: Add logging to SHAMapStore test 2025-07-21 18:19:53 -04:00
Ed Hennis
0cecc09d71 Tweak timing of SHAMapStore test 2025-07-21 18:19:28 -04:00
Ed Hennis
e091d55561 Try to fix timing of LedgerMaster test 2025-07-21 15:14:42 -04:00
Ed Hennis
69cf18158b TEMP: Change some logging to fatal to diagnose CI failures 2025-07-21 14:32:08 -04:00
Ed Hennis
6513c53817 Improve logging
- There's still a race condition in there
2025-07-21 14:30:49 -04:00
Ed Hennis
e13baa58a5 Fix build errors 2025-07-21 13:31:09 -04:00
Ed Hennis
951056fe9b Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-18 18:33:10 -04:00
Ed Hennis
67700ea6bd Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-16 12:54:23 -04:00
Ed Hennis
e5442cf3f1 Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-15 19:36:25 -04:00
Ed Hennis
da68076f04 Change default recovery wait time to 1s
See https://github.com/XRPLF/rippled/pull/5531#issuecomment-3058218837
2025-07-14 14:13:32 -04:00
Ed Hennis
b24116a118 Improve locking, logging, and test output
- Add more info to the error message on some failed tests.
- Add logging details to SHAMapStoreImp
2025-07-14 14:13:27 -04:00
Ed Hennis
f67398c6bf Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-11 19:27:31 -04:00
Ed Hennis
43d3eb1a24 Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-10 21:25:22 -04:00
Ed Hennis
0993315ed5 Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-10 12:30:18 -04:00
Ed Hennis
0bc383ada9 Fix build errors 2025-07-08 20:15:05 -04:00
Ed Hennis
1841ceca43 Add more logging to SHAMapStore rotation 2025-07-08 16:05:03 -04:00
Ed Hennis
2714cebabd Revert "TEMP: Change logging to show progress during unit test"
This reverts commit e184db4ce2.
2025-07-08 16:02:45 -04:00
Ed Hennis
e184db4ce2 TEMP: Change logging to show progress during unit test 2025-07-08 16:02:19 -04:00
Ed Hennis
ac6dc6943c Tweak when the starting range of ledger gap detection is set
- Add a test to exercise online delete ledger gap detection
2025-07-08 16:01:23 -04:00
Ed Hennis
ddd53806df Add a test to exercise LedgerMaster::missingFromCompleteLedgerRange 2025-07-08 13:22:47 -04:00
Ed Hennis
e629a1f70e Merge branch 'develop' into ximinez/online-delete-gaps 2025-07-03 15:51:23 -04:00
Ed Hennis
68076d969c fixup! fixup! Pause online delete if there any any gaps in recent ledger history 2025-07-02 19:05:06 -04:00
Ed Hennis
d3009d3e1c fixup! Pause online delete if there any any gaps in recent ledger history 2025-07-02 18:51:42 -04:00
Ed Hennis
54f7f3c894 Pause online delete if there any any gaps in recent ledger history 2025-07-02 18:45:48 -04:00
30 changed files with 697 additions and 887 deletions

View File

@@ -1059,10 +1059,11 @@
# The online delete process checks periodically
# that xrpld is still in sync with the network,
# and that the validated ledger is less than
# 'age_threshold_seconds' old. If not, then continue
# 'age_threshold_seconds' old, and that all
# recent ledgers are available. If not, then continue
# sleeping for this number of seconds and
# checking until healthy.
# Default is 5.
# Default is 2.
#
# Notes:
# The 'node_db' entry configures the primary, persistent storage.

View File

@@ -23,9 +23,13 @@ checkTxPermission(SLE::const_ref delegate, STTx const& tx);
* @param delegate The delegate account.
* @param type Used to determine which granted granular permissions to load,
* based on the transaction type.
* @return the granted granular permissions tied to the transaction type.
* @param granularPermissions Granted granular permissions tied to the
* transaction type.
*/
std::unordered_set<GranularPermissionType>
getGranularPermission(SLE::const_ref delegate, TxType const& type);
void
loadGranularPermission(
SLE::const_ref delegate,
TxType const& type,
std::unordered_set<GranularPermissionType>& granularPermissions);
} // namespace xrpl

View File

@@ -7,13 +7,8 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace xrpl {
class STTx;
/**
* We have both transaction type permissions and granular type permissions.
* Since we will reuse the TransactionFormats to parse the Transaction
@@ -24,15 +19,15 @@ class STTx;
// Macro-generated, complex
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum GranularPermissionType : std::uint32_t {
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#pragma push_macro("PERMISSION")
#undef PERMISSION
#define GRANULAR_PERMISSION(name, txType, value, ...) name = (value),
#define PERMISSION(type, txType, value) type = (value),
#include <xrpl/protocol/detail/permissions.macro>
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
#undef PERMISSION
#pragma pop_macro("PERMISSION")
};
// Injected bare enumerators (xrpl::delegable / xrpl::notDelegable) are required by preprocessor
@@ -45,30 +40,15 @@ class Permission
private:
Permission();
struct GranularPermissionEntry
{
std::string name;
TxType txType;
std::uint32_t permittedFlags;
SOTemplate permittedFields;
std::unordered_map<std::uint16_t, uint256> txFeatureMap_;
GranularPermissionEntry(
std::string name,
TxType txType,
std::uint32_t permittedFlags,
std::vector<SOElement> fields);
};
std::unordered_map<std::uint16_t, Delegation> delegableTx_;
struct TxDelegationEntry
{
uint256 amendment;
Delegation delegable{NotDelegable};
};
std::unordered_map<std::string, GranularPermissionType> granularPermissionMap_;
std::unordered_set<TxType> granularTxTypes_;
std::unordered_map<TxType, TxDelegationEntry> txDelegationMap_;
std::unordered_map<std::string, GranularPermissionType> granularPermissionsByName_;
std::unordered_map<GranularPermissionType, GranularPermissionEntry> granularPermissions_;
std::unordered_map<GranularPermissionType, std::string> granularNameMap_;
std::unordered_map<GranularPermissionType, TxType> granularTxTypeMap_;
public:
static Permission const&
@@ -79,52 +59,30 @@ public:
operator=(Permission const&) = delete;
[[nodiscard]] std::optional<std::string>
getPermissionName(std::uint32_t value) const;
getPermissionName(std::uint32_t const value) const;
[[nodiscard]] std::optional<std::uint32_t>
getGranularValue(std::string const& name) const;
[[nodiscard]] std::optional<std::string>
getGranularName(GranularPermissionType value) const;
getGranularName(GranularPermissionType const& value) const;
[[nodiscard]] std::optional<TxType>
getGranularTxType(GranularPermissionType gpType) const;
getGranularTxType(GranularPermissionType const& gpType) const;
// Returns a reference to avoid copying uint256 - 32 bytes. std::optional
// cannot hold references directly, so std::reference_wrapper is used.
[[nodiscard]] std::optional<std::reference_wrapper<uint256 const>>
getTxFeature(TxType txType) const;
[[nodiscard]] bool
isDelegable(std::uint32_t permissionValue, Rules const& rules) const;
[[nodiscard]] bool
hasGranularPermissions(TxType txType) const;
isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const;
// for tx level permission, permission value is equal to tx type plus one
[[nodiscard]] static uint32_t
txToPermissionType(TxType type);
static uint32_t
txToPermissionType(TxType const& type);
// tx type value is permission value minus one
[[nodiscard]] static TxType
permissionToTxType(std::uint32_t value);
/**
* @brief Verifies a delegated transaction against its granular permission template.
*
* @note WARNING: Do not move this check before standard transaction-level
* format checks, which is in preclaim. This function assumes the transaction's
* base structural integrity (fees, sequence, signatures) has already been
* validated.
*
* @param tx The transaction to verify.
* @param heldPermissions The granular permissions that the sender hold.
* @return true if the transaction fields and flags comply with the granular template.
*/
[[nodiscard]] bool
checkGranularSandbox(
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldPermissions) const;
static TxType
permissionToTxType(uint32_t const& value);
};
} // namespace xrpl

View File

@@ -21,7 +21,7 @@ XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultN
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (IncludeKeyletFields, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicMPT, Supported::No, VoteBehavior::DefaultNo)

View File

@@ -1,74 +1,49 @@
#if !defined(GRANULAR_PERMISSION)
#error "undefined macro: GRANULAR_PERMISSION"
#if !defined(PERMISSION)
#error "undefined macro: PERMISSION"
#endif
/**
* GRANULAR_PERMISSION(name, txType, value, allowedFlags, allowedFields)
* PERMISSION(name, type, txType, value)
*
* Defines a granular permission:
* name: the granular permission name.
* txType: the corresponding TxType for this permission.
* value: the uint32 numeric value for the enum type.
* allowedFlags: transaction flags permitted under this permission.
* allowedFields: transaction fields permitted under this permission.
* This macro defines a permission:
* name: the name of the permission.
* type: the GranularPermissionType enum.
* txType: the corresponding TxType for this permission.
* value: the uint32 numeric value for the enum type.
*/
/** Grants the ability to authorize a trustline. */
GRANULAR_PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537, tfUniversal | tfSetfAuth,
({{sfLimitAmount, SoeRequired}}))
/** This permission grants the delegated account the ability to authorize a trustline. */
PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537)
/** Grants the ability to freeze a trustline. */
GRANULAR_PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538, tfUniversal | tfSetFreeze,
({{sfLimitAmount, SoeRequired}}))
/** This permission grants the delegated account the ability to freeze a trustline. */
PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538)
/** Grants the ability to unfreeze a trustline. */
GRANULAR_PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539, tfUniversal | tfClearFreeze,
({{sfLimitAmount, SoeRequired}}))
/** This permission grants the delegated account the ability to unfreeze a trustline. */
PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539)
/** Grants the ability to set Domain. */
GRANULAR_PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540, tfUniversal,
({{sfDomain, SoeOptional}}))
/** This permission grants the delegated account the ability to set Domain. */
PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540)
/** Grants the ability to set EmailHash. */
GRANULAR_PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541, tfUniversal,
({{sfEmailHash, SoeOptional}}))
/** This permission grants the delegated account the ability to set EmailHashSet. */
PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541)
/** Grants the ability to set MessageKey. */
GRANULAR_PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542, tfUniversal,
({{sfMessageKey, SoeOptional}}))
/** This permission grants the delegated account the ability to set MessageKey. */
PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542)
/** Grants the ability to set TransferRate. */
GRANULAR_PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543, tfUniversal,
({{sfTransferRate, SoeOptional}}))
/** This permission grants the delegated account the ability to set TransferRate. */
PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543)
/** Grants the ability to set TickSize. */
GRANULAR_PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544, tfUniversal,
({{sfTickSize, SoeOptional}}))
/** This permission grants the delegated account the ability to set TickSize. */
PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544)
/** Grants the ability to mint payment (sending account is the issuer). Cross-currency payments are disallowed. */
GRANULAR_PERMISSION(PaymentMint, ttPAYMENT, 65545, tfUniversal,
({{sfDestination, SoeRequired},
{sfAmount, SoeRequired},
{sfSendMax, SoeOptional},
{sfInvoiceID, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfCredentialIDs, SoeOptional}}))
/** This permission grants the delegated account the ability to mint payment, which means sending a payment for a currency where the sending account is the issuer. */
PERMISSION(PaymentMint, ttPAYMENT, 65545)
/** Grants the ability to burn payment (destination account is the issuer). Cross-currency payments are disallowed. */
GRANULAR_PERMISSION(PaymentBurn, ttPAYMENT, 65546, tfUniversal,
({{sfDestination, SoeRequired},
{sfAmount, SoeRequired},
{sfSendMax, SoeOptional},
{sfInvoiceID, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfCredentialIDs, SoeOptional}}))
/** This permission grants the delegated account the ability to burn payment, which means sending a payment for a currency where the destination account is the issuer */
PERMISSION(PaymentBurn, ttPAYMENT, 65546)
/** Grants the ability to lock an MPToken. */
GRANULAR_PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547, tfUniversal | tfMPTLock,
({{sfMPTokenIssuanceID, SoeRequired},
{sfHolder, SoeOptional}}))
/** This permission grants the delegated account the ability to lock MPToken. */
PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547)
/** Grants the ability to unlock an MPToken. */
GRANULAR_PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548, tfUniversal | tfMPTUnlock,
({{sfMPTokenIssuanceID, SoeRequired},
{sfHolder, SoeOptional}}))
/** This permission grants the delegated account the ability to unlock MPToken. */
PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548)

View File

@@ -222,63 +222,8 @@ public:
return tesSUCCESS;
}
/**
* This function can be overridden to introduce additional semantic constraints beyond the
* granular template validation for granular permissions. It is called by the base
* invokeCheckPermission method only after the transaction has successfully passed
* checkGranularSandbox.
*/
static NotTEC
checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
{
return tesSUCCESS;
}
/**
* Checks whether the transaction is authorized to be executed by the delegated account.
* This function enforces the strict permission check hierarchy. It is explicitly
* designed NOT to be overridden. Derived transactors must instead implement
* checkGranularSemantics to add custom validation logic for granular permissions.
*
* The evaluation proceeds as follows:
* - If transaction-level permission is granted, the function immediately returns tesSUCCESS.
* - If transaction-level permission is not granted, the function checks whether the transaction
* matches the granular permission template defined in permissions.macro. If it does, it then
* calls checkGranularSemantics to perform any additional, fine-grained validation.
*
*/
template <class T>
static NotTEC
invokeCheckPermission(ReadView const& view, STTx const& tx)
{
// heldGranularPermissions is passed by reference into checkPermission.
// It is populated with the senders granular permissions only when the sender
// lacks tx-level permission but has granular permissions that satisfy the
// granular permission template.
//
// - result is terNO_DELEGATE_PERMISSION: return immediately.
// - result is tesSUCCESS and heldGranularPermissions is empty: tx-level permission was
// granted, so we returned success before populating it.
// - result is tesSUCCESS and heldGranularPermissions is not empty: tx-level permission was
// not granted, but the held granular permissions passed checkGranularSandbox, so we proceed
// to checkGranularSemantics.
//
// WARNING: Do not simplify checkPermission to return only
// heldGranularPermissions or the ter code. Both the result and the
// populated set are required to enforce the strict permission hierarchy
// described above.
std::unordered_set<GranularPermissionType> heldGranularPermissions;
if (NotTEC const result = checkPermission(view, tx, heldGranularPermissions);
!isTesSuccess(result) || heldGranularPermissions.empty())
{
return result;
}
return T::checkGranularSemantics(view, tx, heldGranularPermissions);
}
checkPermission(ReadView const& view, STTx const& tx);
/////////////////////////////////////////////////////
// Interface used by AccountDelete
@@ -408,12 +353,6 @@ protected:
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
private:
static NotTEC
checkPermission(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType>& heldGranularPermissions);
std::pair<TER, XRPAmount>
reset(XRPAmount fee);

View File

@@ -23,6 +23,9 @@ public:
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -32,10 +32,7 @@ public:
preflight(PreflightContext const& ctx);
static NotTEC
checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -22,6 +22,9 @@ public:
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -21,10 +21,7 @@ public:
preflight(PreflightContext const& ctx);
static NotTEC
checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -17,10 +17,6 @@ ProtectHome=true
PrivateTmp=true
User=xrpld
Group=xrpld
StateDirectory=xrpld
StateDirectoryMode=0750
LogsDirectory=xrpld
LogsDirectoryMode=0750
LimitNOFILE=65536
SystemCallArchitectures=native

View File

@@ -1,136 +1,91 @@
#include <xrpl/protocol/Permissions.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h> // IWYU pragma: keep
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFlags.h> // IWYU pragma: keep
#include <xrpl/protocol/TxFormats.h>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <optional>
#include <stdexcept>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>
namespace xrpl {
Permission::GranularPermissionEntry::GranularPermissionEntry(
std::string name,
TxType txType,
std::uint32_t permittedFlags,
std::vector<SOElement> permittedFields)
: name(std::move(name))
, txType(txType)
, permittedFlags(permittedFlags)
, permittedFields(std::move(permittedFields), TxFormats::getCommonFields())
{
}
Permission::Permission()
{
{
txFeatureMap_ = {
#pragma push_macro("TRANSACTION")
#undef TRANSACTION
#define TRANSACTION(tag, value, name, delegable, amendment, ...) \
txDelegationMap_[static_cast<TxType>(value)] = {amendment, delegable};
#define TRANSACTION(tag, value, name, delegable, amendment, ...) {value, amendment},
#include <xrpl/protocol/detail/transactions.macro>
#undef TRANSACTION
#pragma pop_macro("TRANSACTION")
}
granularPermissionsByName_ = {
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#define GRANULAR_PERMISSION(type, ...) {#type, type},
#include <xrpl/protocol/detail/permissions.macro>
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
};
{
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
delegableTx_ = {
#pragma push_macro("TRANSACTION")
#undef TRANSACTION
// NOLINTBEGIN(bugprone-macro-parentheses)
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) \
granularPermissions_.emplace( \
std::piecewise_construct, \
std::forward_as_tuple(GranularPermissionType::type), \
std::forward_as_tuple( \
#type, txType, static_cast<std::uint32_t>(flags), std::vector<SOElement> fields));
// NOLINTEND(bugprone-macro-parentheses)
#define TRANSACTION(tag, value, name, delegable, ...) {value, delegable},
#include <xrpl/protocol/detail/transactions.macro>
#undef TRANSACTION
#pragma pop_macro("TRANSACTION")
};
granularPermissionMap_ = {
#pragma push_macro("PERMISSION")
#undef PERMISSION
#define PERMISSION(type, txType, value) {#type, type},
#include <xrpl/protocol/detail/permissions.macro>
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
}
#undef PERMISSION
#pragma pop_macro("PERMISSION")
};
if (granularPermissionsByName_.size() != granularPermissions_.size())
granularNameMap_ = {
#pragma push_macro("PERMISSION")
#undef PERMISSION
#define PERMISSION(type, txType, value) {type, #type},
#include <xrpl/protocol/detail/permissions.macro>
#undef PERMISSION
#pragma pop_macro("PERMISSION")
};
granularTxTypeMap_ = {
#pragma push_macro("PERMISSION")
#undef PERMISSION
#define PERMISSION(type, txType, value) {type, txType},
#include <xrpl/protocol/detail/permissions.macro>
#undef PERMISSION
#pragma pop_macro("PERMISSION")
};
XRPL_ASSERT(
txFeatureMap_.size() == delegableTx_.size(),
"xrpl::Permission : txFeatureMap_ and delegableTx_ must have same "
"size");
for ([[maybe_unused]] auto const& permission : granularPermissionMap_)
{
// LCOV_EXCL_START
Throw<std::logic_error>(
"granularPermissionsByName_ and granularPermissions_ must have same size");
// LCOV_EXCL_STOP
}
for (auto const& [name, type] : granularPermissionsByName_)
{
if (type <= UINT16_MAX)
{
// LCOV_EXCL_START
Throw<std::logic_error>(
"Granular permission value must exceed the maximum uint16_t value: " + name);
// LCOV_EXCL_STOP
}
}
for (auto const& [type, entry] : granularPermissions_)
granularTxTypes_.insert(entry.txType);
// Validate that all fields listed in permissions.macro exist in the
// corresponding transaction type's format, catching typos at startup.
for (auto const& [type, entry] : granularPermissions_)
{
if (!txDelegationMap_.contains(entry.txType))
{
// LCOV_EXCL_START
Throw<std::logic_error>("Invalid granular permission txType in txDelegationMap_");
// LCOV_EXCL_STOP
}
auto const* fmt = TxFormats::getInstance().findByType(entry.txType);
if (fmt == nullptr)
{
// LCOV_EXCL_START
Throw<std::logic_error>("Invalid granular permission txType");
// LCOV_EXCL_STOP
}
for (auto const& field : entry.permittedFields)
{
if (fmt->getSOTemplate().getIndex(field.sField()) == -1)
{
// LCOV_EXCL_START
Throw<std::logic_error>("Invalid granular permission field");
// LCOV_EXCL_STOP
}
}
XRPL_ASSERT(
permission.second > UINT16_MAX,
"xrpl::Permission::granularPermissionMap_ : granular permission "
"value must not exceed the maximum uint16_t value.");
}
}
@@ -142,11 +97,8 @@ Permission::getInstance()
}
std::optional<std::string>
Permission::getPermissionName(std::uint32_t value) const
Permission::getPermissionName(std::uint32_t const value) const
{
if (value == 0)
return std::nullopt;
auto const permissionValue = static_cast<GranularPermissionType>(value);
if (auto const granular = getGranularName(permissionValue))
return granular;
@@ -162,131 +114,90 @@ Permission::getPermissionName(std::uint32_t value) const
std::optional<std::uint32_t>
Permission::getGranularValue(std::string const& name) const
{
auto const it = granularPermissionsByName_.find(name);
if (it != granularPermissionsByName_.end())
auto const it = granularPermissionMap_.find(name);
if (it != granularPermissionMap_.end())
return static_cast<uint32_t>(it->second);
return std::nullopt;
}
std::optional<std::string>
Permission::getGranularName(GranularPermissionType value) const
Permission::getGranularName(GranularPermissionType const& value) const
{
auto const it = granularPermissions_.find(value);
if (it != granularPermissions_.end())
return it->second.name;
auto const it = granularNameMap_.find(value);
if (it != granularNameMap_.end())
return it->second;
return std::nullopt;
}
std::optional<TxType>
Permission::getGranularTxType(GranularPermissionType gpType) const
Permission::getGranularTxType(GranularPermissionType const& gpType) const
{
auto const it = granularPermissions_.find(gpType);
if (it != granularPermissions_.end())
return it->second.txType;
auto const it = granularTxTypeMap_.find(gpType);
if (it != granularTxTypeMap_.end())
return it->second;
return std::nullopt;
}
bool
Permission::hasGranularPermissions(TxType txType) const
{
return granularTxTypes_.contains(txType);
}
std::optional<std::reference_wrapper<uint256 const>>
Permission::getTxFeature(TxType txType) const
{
auto const it = txDelegationMap_.find(txType);
auto const txFeaturesIt = txFeatureMap_.find(txType);
XRPL_ASSERT(
it != txDelegationMap_.end(),
"xrpl::Permission::getTxFeature : tx exists in txDelegationMap_");
txFeaturesIt != txFeatureMap_.end(),
"xrpl::Permissions::getTxFeature : tx exists in txFeatureMap_");
if (it->second.amendment == uint256{})
if (txFeaturesIt->second == uint256{})
return std::nullopt;
return std::optional{std::cref(it->second.amendment)};
return txFeaturesIt->second;
}
bool
Permission::isDelegable(std::uint32_t permissionValue, Rules const& rules) const
Permission::isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const
{
if (permissionValue == 0)
return false; // LCOV_EXCL_LINE
auto const amendmentEnabled = [&rules](TxDelegationEntry const& entry) {
return entry.amendment == uint256{} || rules.enabled(entry.amendment);
};
// Granular permissions may authorize a limited subset of a tx type even
// when the full tx type is not delegable. They still require the
// underlying transaction amendment to be enabled.
if (auto const granularIt =
granularPermissions_.find(static_cast<GranularPermissionType>(permissionValue));
granularIt != granularPermissions_.end())
auto const granularPermission =
getGranularName(static_cast<GranularPermissionType>(permissionValue));
if (granularPermission)
{
auto const txIt = txDelegationMap_.find(granularIt->second.txType);
return txIt != txDelegationMap_.end() && amendmentEnabled(txIt->second);
// granular permissions are always allowed to be delegated
return true;
}
auto const txType = permissionToTxType(permissionValue);
auto const txIt = txDelegationMap_.find(txType);
auto const it = delegableTx_.find(txType);
// Tx-level permissions require the transaction type itself to be delegable, and
// the corresponding amendment enabled.
return txIt != txDelegationMap_.end() && txIt->second.delegable != NotDelegable &&
amendmentEnabled(txIt->second);
if (it == delegableTx_.end())
return false;
auto const txFeaturesIt = txFeatureMap_.find(txType);
XRPL_ASSERT(
txFeaturesIt != txFeatureMap_.end(),
"xrpl::Permissions::isDelegable : tx exists in txFeatureMap_");
// Delegation is only allowed if the required amendment for the transaction
// is enabled. For transactions that do not require an amendment, delegation
// is always allowed.
if (txFeaturesIt->second != uint256{} && !rules.enabled(txFeaturesIt->second))
return false;
if (it->second == Delegation::NotDelegable)
return false;
return true;
}
uint32_t
Permission::txToPermissionType(TxType const type)
Permission::txToPermissionType(TxType const& type)
{
return static_cast<uint32_t>(type) + 1;
}
TxType
Permission::permissionToTxType(uint32_t value)
Permission::permissionToTxType(uint32_t const& value)
{
XRPL_ASSERT(value > 0, "xrpl::Permission::permissionToTxType : value is greater than 0");
return static_cast<TxType>(value - 1);
}
bool
Permission::checkGranularSandbox(
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldPermissions) const
{
// Build union of flags upfront to enable an early exit. Fields are not stored and
// grouped in advance to avoid heap allocation.
std::uint32_t unionFlags = 0;
for (auto const& gp : heldPermissions)
{
auto const it = granularPermissions_.find(gp);
if (it != granularPermissions_.end())
unionFlags |= it->second.permittedFlags;
}
// Check if flags are permitted
if ((tx.getFlags() & ~unionFlags) != 0)
return false;
// Check if fields are permitted. Every present field must appear in at least one held
// permission's template. The common fields are included in the constructor.
for (auto const& field : tx)
{
if (field.getSType() == STI_NOTPRESENT)
continue;
if (!std::ranges::any_of(heldPermissions, [&](auto const& gp) {
auto const it = granularPermissions_.find(gp);
return it != granularPermissions_.end() &&
it->second.permittedFields.getIndex(field.getFName()) != -1;
}))
return false;
}
return true;
}
} // namespace xrpl

View File

@@ -217,7 +217,7 @@ STTx::getFeePayer() const
{
// If sfDelegate is present, the delegate account is the payer
// note: if a delegate is specified, its authorization to act on behalf of the account is
// enforced in `Transactor::invokeCheckPermission`
// enforced in `Transactor::checkPermission`
// cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`)
if (isFieldPresent(sfDelegate))
return getAccountID(sfDelegate);

View File

@@ -22,7 +22,6 @@
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rules.h>
@@ -47,7 +46,6 @@
#include <exception>
#include <optional>
#include <stdexcept>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -177,16 +175,6 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
if (ctx.tx[sfDelegate] == ctx.tx[sfAccount])
return temBAD_SIGNER;
auto const& perm = Permission::getInstance();
auto const txType = ctx.tx.getTxnType();
// If the transaction is not delegable and does not have granular permissions, fail earlier
// with temINVALID. This is to prevent transactions that are not delegable at all from
// being processed further in the invokeCheckPermission function.
if (!perm.isDelegable(Permission::txToPermissionType(txType), ctx.rules) &&
!perm.hasGranularPermissions(txType))
return temINVALID;
}
if (auto const ret = preflight0(ctx, flagMask))
@@ -307,33 +295,19 @@ Transactor::preflightSigValidated(PreflightContext const& ctx)
}
NotTEC
Transactor::checkPermission(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType>& heldGranularPermissions)
Transactor::checkPermission(ReadView const& view, STTx const& tx)
{
auto const delegate = tx[~sfDelegate];
if (!delegate)
return tesSUCCESS;
auto const sle = view.read(keylet::delegate(tx[sfAccount], *delegate));
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
auto const sle = view.read(delegateKey);
if (!sle)
return terNO_DELEGATE_PERMISSION;
if (isTesSuccess(checkTxPermission(sle, tx)))
return tesSUCCESS;
if (!Permission::getInstance().hasGranularPermissions(tx.getTxnType()))
return terNO_DELEGATE_PERMISSION;
heldGranularPermissions = getGranularPermission(sle, tx.getTxnType());
if (heldGranularPermissions.empty())
return terNO_DELEGATE_PERMISSION;
if (!Permission::getInstance().checkGranularSandbox(tx, heldGranularPermissions))
return terNO_DELEGATE_PERMISSION;
return tesSUCCESS;
return checkTxPermission(sle, tx);
}
XRPAmount

View File

@@ -181,8 +181,7 @@ invokePreclaim(PreclaimContext const& ctx)
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
return result;
if (NotTEC const result =
Transactor::invokeCheckPermission<T>(ctx.view, ctx.tx))
if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx))
return result;
if (NotTEC const result = T::checkSign(ctx))

View File

@@ -6,6 +6,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -19,11 +20,13 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/applySteps.h>
#include <cstdint>
#include <unordered_set>
namespace xrpl {
@@ -165,6 +168,54 @@ AccountSet::preflight(PreflightContext const& ctx)
return tesSUCCESS;
}
NotTEC
AccountSet::checkPermission(ReadView const& view, STTx const& tx)
{
// AccountSet is prohibited to be granted on a transaction level,
// but some granular permissions are allowed.
auto const delegate = tx[~sfDelegate];
if (!delegate)
return tesSUCCESS;
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
auto const sle = view.read(delegateKey);
if (!sle)
return terNO_DELEGATE_PERMISSION;
std::unordered_set<GranularPermissionType> granularPermissions;
loadGranularPermission(sle, ttACCOUNT_SET, granularPermissions);
auto const uSetFlag = tx.getFieldU32(sfSetFlag);
auto const uClearFlag = tx.getFieldU32(sfClearFlag);
// We don't support any flag based granular permission under
// AccountSet transaction. If any delegated account is trying to
// update the flag on behalf of another account, it is not
// authorized.
if (uSetFlag != 0 || uClearFlag != 0 || ((tx.getFlags() & tfUniversalMask) != 0u))
return terNO_DELEGATE_PERMISSION;
if (tx.isFieldPresent(sfEmailHash) && !granularPermissions.contains(AccountEmailHashSet))
return terNO_DELEGATE_PERMISSION;
if (tx.isFieldPresent(sfWalletLocator) || tx.isFieldPresent(sfNFTokenMinter))
return terNO_DELEGATE_PERMISSION;
if (tx.isFieldPresent(sfMessageKey) && !granularPermissions.contains(AccountMessageKeySet))
return terNO_DELEGATE_PERMISSION;
if (tx.isFieldPresent(sfDomain) && !granularPermissions.contains(AccountDomainSet))
return terNO_DELEGATE_PERMISSION;
if (tx.isFieldPresent(sfTransferRate) && !granularPermissions.contains(AccountTransferRateSet))
return terNO_DELEGATE_PERMISSION;
if (tx.isFieldPresent(sfTickSize) && !granularPermissions.contains(AccountTickSizeSet))
return terNO_DELEGATE_PERMISSION;
return tesSUCCESS;
}
TER
AccountSet::preclaim(PreclaimContext const& ctx)
{

View File

@@ -29,12 +29,14 @@ checkTxPermission(SLE::const_ref delegate, STTx const& tx)
return terNO_DELEGATE_PERMISSION;
}
std::unordered_set<GranularPermissionType>
getGranularPermission(SLE::const_ref delegate, TxType const& txType)
void
loadGranularPermission(
SLE::const_ref delegate,
TxType const& txType,
std::unordered_set<GranularPermissionType>& granularPermissions)
{
std::unordered_set<GranularPermissionType> granularPermissions;
if (!delegate)
return granularPermissions;
return;
auto const permissionArray = delegate->getFieldArray(sfPermissions);
for (auto const& permission : permissionArray)
@@ -45,8 +47,6 @@ getGranularPermission(SLE::const_ref delegate, TxType const& txType)
if (type && *type == txType)
granularPermissions.insert(granularValue);
}
return granularPermissions;
}
} // namespace xrpl

View File

@@ -8,6 +8,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -28,6 +29,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
@@ -271,24 +273,38 @@ Payment::preflight(PreflightContext const& ctx)
}
NotTEC
Payment::checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
Payment::checkPermission(ReadView const& view, STTx const& tx)
{
auto const delegate = tx[~sfDelegate];
if (!delegate)
return tesSUCCESS;
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
auto const sle = view.read(delegateKey);
if (!sle)
return terNO_DELEGATE_PERMISSION;
if (isTesSuccess(checkTxPermission(sle, tx)))
return tesSUCCESS;
std::unordered_set<GranularPermissionType> granularPermissions;
loadGranularPermission(sle, ttPAYMENT, granularPermissions);
auto const& dstAmount = tx.getFieldAmount(sfAmount);
auto const& amountAsset = dstAmount.asset();
// Granular permissions are only valid for direct payments.
if (tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
if ((tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset) ||
tx.isFieldPresent(sfPaths))
return terNO_DELEGATE_PERMISSION;
// PaymentMint and PaymentBurn apply to both IOU and MPT direct payments.
if (heldGranularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
amountAsset.getIssuer() == tx[sfAccount])
return tesSUCCESS;
if (heldGranularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) &&
if (granularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) &&
amountAsset.getIssuer() == tx[sfDestination])
return tesSUCCESS;

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -15,12 +16,14 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <unordered_set>
namespace xrpl {
@@ -135,6 +138,39 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
return tesSUCCESS;
}
NotTEC
MPTokenIssuanceSet::checkPermission(ReadView const& view, STTx const& tx)
{
auto const delegate = tx[~sfDelegate];
if (!delegate)
return tesSUCCESS;
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
auto const sle = view.read(delegateKey);
if (!sle)
return terNO_DELEGATE_PERMISSION;
if (isTesSuccess(checkTxPermission(sle, tx)))
return tesSUCCESS;
// this is added in case more flags will be added for MPTokenIssuanceSet
// in the future. Currently unreachable.
if ((tx.getFlags() & tfMPTokenIssuanceSetMask) != 0u)
return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE
std::unordered_set<GranularPermissionType> granularPermissions;
loadGranularPermission(sle, ttMPTOKEN_ISSUANCE_SET, granularPermissions);
if (tx.isFlag(tfMPTLock) && !granularPermissions.contains(MPTokenIssuanceLock))
return terNO_DELEGATE_PERMISSION;
if (tx.isFlag(tfMPTUnlock) && !granularPermissions.contains(MPTokenIssuanceUnlock))
return terNO_DELEGATE_PERMISSION;
return tesSUCCESS;
}
TER
MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
{

View File

@@ -6,6 +6,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
@@ -20,6 +21,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
@@ -122,21 +124,51 @@ TrustSet::preflight(PreflightContext const& ctx)
}
NotTEC
TrustSet::checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
TrustSet::checkPermission(ReadView const& view, STTx const& tx)
{
auto const delegate = tx[~sfDelegate];
if (!delegate)
return tesSUCCESS;
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
auto const sle = view.read(delegateKey);
if (!sle)
return terNO_DELEGATE_PERMISSION;
if (isTesSuccess(checkTxPermission(sle, tx)))
return tesSUCCESS;
// Currently we only support TrustlineAuthorize, TrustlineFreeze and
// TrustlineUnfreeze granular permission. Setting other flags returns
// error.
if ((tx.getFlags() & tfTrustSetPermissionMask) != 0u)
return terNO_DELEGATE_PERMISSION;
if (tx.isFieldPresent(sfQualityIn) || tx.isFieldPresent(sfQualityOut))
return terNO_DELEGATE_PERMISSION;
auto const saLimitAmount = tx.getFieldAmount(sfLimitAmount);
auto const sleRippleState = view.read(
keylet::line(
tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.get<Issue>().currency));
// granular permissions are not allowed to create a trustline
// if the trustline does not exist, granular permissions are
// not allowed to create trustline
if (!sleRippleState)
return terNO_DELEGATE_PERMISSION;
// updating LimitAmount is not allowed with granular permissions,
std::unordered_set<GranularPermissionType> granularPermissions;
loadGranularPermission(sle, ttTRUST_SET, granularPermissions);
if (tx.isFlag(tfSetfAuth) && !granularPermissions.contains(TrustlineAuthorize))
return terNO_DELEGATE_PERMISSION;
if (tx.isFlag(tfSetFreeze) && !granularPermissions.contains(TrustlineFreeze))
return terNO_DELEGATE_PERMISSION;
if (tx.isFlag(tfClearFreeze) && !granularPermissions.contains(TrustlineUnfreeze))
return terNO_DELEGATE_PERMISSION;
// updating LimitAmount is not allowed only with granular permissions,
// unless there's a new granular permission for this in the future.
auto const curLimit = tx[sfAccount] > saLimitAmount.getIssuer()
? sleRippleState->getFieldAmount(sfHighLimit)

View File

@@ -5,11 +5,8 @@
#include <test/jtx/acctdelete.h>
#include <test/jtx/amount.h>
#include <test/jtx/balance.h>
#include <test/jtx/batch.h>
#include <test/jtx/delegate.h>
#include <test/jtx/delivermin.h>
#include <test/jtx/did.h>
#include <test/jtx/domain.h>
#include <test/jtx/fee.h>
#include <test/jtx/flags.h>
#include <test/jtx/mpt.h>
@@ -25,9 +22,7 @@
#include <test/jtx/ter.h>
#include <test/jtx/trust.h>
#include <test/jtx/txflags.h>
#include <test/jtx/vault.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
@@ -38,7 +33,6 @@
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/SField.h>
@@ -47,7 +41,6 @@
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
@@ -1070,93 +1063,6 @@ class Delegate_test : public beast::unit_test::Suite
}
}
// PaymentMint/PaymentBurn with sfSendMax of the same asset is allowed,
// same-asset SendMax is still a direct payment, not cross-currency.
{
Env env(*this, features);
Account const alice{"alice"};
Account const bob{"bob"};
Account const gw{"gw"};
auto const usd = gw["USD"];
env.fund(XRP(10000), alice, bob, gw);
env.trust(usd(200), alice);
env.close();
env(delegate::set(gw, bob, {"PaymentMint"}));
env.close();
// sfSendMax with same asset as sfAmount, still a direct payment
env(pay(gw, alice, usd(50)), Sendmax(usd(50)), delegate::As(bob));
env.require(Balance(alice, usd(50)));
env(delegate::set(alice, bob, {"PaymentBurn"}));
env.close();
env(pay(alice, gw, usd(30)), Sendmax(usd(30)), delegate::As(bob));
env.require(Balance(alice, usd(20)));
}
// Test invalid fields or flags not allowed in granular permission template
{
Env env(*this, features);
Account const alice{"alice"};
Account const bob{"bob"};
Account const gw{"gw"};
auto const usd = gw["USD"];
env.fund(XRP(10000), alice, bob, gw);
env.trust(usd(200), alice);
env.close();
env(delegate::set(gw, bob, {"PaymentMint"}));
env(delegate::set(alice, bob, {"PaymentBurn"}));
env.close();
// sfDeliverMin (with tfPartialPayment) is not in the PaymentMint
// or PaymentBurn template.
env(pay(gw, alice, usd(100)),
DeliverMin(usd(50)),
Txflags(tfPartialPayment),
delegate::As(bob),
Ter(terNO_DELEGATE_PERMISSION));
env(pay(alice, gw, usd(50)),
DeliverMin(usd(25)),
Txflags(tfPartialPayment),
delegate::As(bob),
Ter(terNO_DELEGATE_PERMISSION));
// sfDomainID is not in the PaymentMint or PaymentBurn template.
env(pay(gw, alice, usd(100)),
Domain(uint256{1}),
delegate::As(bob),
Ter(terNO_DELEGATE_PERMISSION));
env(pay(alice, gw, usd(50)),
Domain(uint256{1}),
delegate::As(bob),
Ter(terNO_DELEGATE_PERMISSION));
}
// Delegate account holds no granular permissions for the tx type:
// getGranularPermission returns empty set.
{
Env env(*this, features);
Account const alice{"alice"};
Account const bob{"bob"};
Account const gw{"gw"};
auto const usd = gw["USD"];
env.fund(XRP(10000), alice, bob, gw);
env.trust(usd(200), alice);
env.close();
// Bob holds only an AccountSet granular permission.
env(delegate::set(alice, bob, {"AccountDomainSet"}));
env.close();
// Payment has granular permissions defined in permissions.macro,
// but bob only holds AccountSet's granular permission,
// getGranularPermission returns empty.
env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
}
// PaymentMint and PaymentBurn for MPT
{
std::string logs;
@@ -1213,40 +1119,6 @@ class Delegate_test : public beast::unit_test::Suite
BEAST_EXPECT(env.balance(bob, MPT) == bobMPT + MPT(100));
}
}
// Verify granular permissions of different tx types in the same SLE are scoped
// correctly. AccountSet permissions don't apply to Payment and vice versa
{
Env env(*this);
Account const alice{"alice"};
Account const bob{"bob"};
Account const gw{"gw"};
auto const usd = gw["USD"];
env.fund(XRP(10000), alice, bob, gw);
env.trust(usd(200), alice);
env.close();
// Alice granted bob with both AccountDomainSet and PaymentMint.
env(delegate::set(alice, bob, {"AccountDomainSet", "PaymentMint"}));
env.close();
// PaymentMint fails at granular semantic check because alice is not the issuer.
env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
// AccountDomainSet applies correctly to AccountSet
std::string const domain = "example.com";
auto jt = noop(alice);
jt[sfDomain] = strHex(domain);
jt[sfDelegate] = bob.human();
env(jt);
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
// gw gives bob PaymentMint and bob can mint on gw's behalf
env(delegate::set(gw, bob, {"PaymentMint"}));
env.close();
env(pay(gw, alice, usd(50)), delegate::As(bob));
env.require(Balance(alice, usd(50)));
}
}
void
@@ -1429,34 +1301,6 @@ class Delegate_test : public beast::unit_test::Suite
env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfFullyCanonicalSig),
delegate::As(bob));
}
{
Env env(*this);
Account const gw{"gw"};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), gw, alice, bob);
env(fset(gw, asfRequireAuth));
env.close();
env(trust(alice, gw["USD"](50)));
env.close();
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
env.close();
env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
env.close();
// sfQualityOut is a valid TrustSet field, but not permitted in granular template
json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
txJson[sfQualityOut.jsonName] = 100;
env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
// tfSetNoRipple is a valid flag for TrustSet, but not permitted in granular template
env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfSetNoRipple),
delegate::As(bob),
Ter(terNO_DELEGATE_PERMISSION));
}
}
void
@@ -1612,9 +1456,7 @@ class Delegate_test : public beast::unit_test::Suite
env(jv2, Ter(terNO_DELEGATE_PERMISSION));
}
// can not set AccountSet flags on behalf of other account,
// in permissions.macro, the template for AccountSet does
// not allow any flag set or clear.
// can not set AccountSet flags on behalf of other account
{
Env env(*this);
auto const alice = Account{"alice"};
@@ -1710,71 +1552,6 @@ class Delegate_test : public beast::unit_test::Suite
env(jt);
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
}
// setting invalid field not in permissions.macro template will be rejected.
{
Env env(*this);
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
env.fund(XRP(10000), alice, bob);
env.close();
// Alice gives Bob permission to set her Domain
env(delegate::set(alice, bob, {"AccountDomainSet"}));
env.close();
std::string const domain = "example.com";
auto txJson = noop(alice);
txJson[sfDomain] = strHex(domain);
txJson[sfDelegate] = bob.human();
// sfNFTokenMinter is a valid field in AccountSet tx, but
// it is not permitted for granular template
txJson[sfNFTokenMinter] = bob.human();
env(txJson, Ter(terNO_DELEGATE_PERMISSION));
}
// Delegated AccountSet with no fields and no flags is allowed,
// because it is allowed in the non-delegated case as well.
{
Env env(*this);
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice, bob);
env.close();
env(delegate::set(alice, bob, {"AccountDomainSet"}));
env.close();
auto jt = noop(alice);
jt[sfDelegate] = bob.human();
env(jt);
}
// Revoking all permissions deletes the SLE and subsequent attempts are rejected.
{
Env env(*this);
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice, bob);
env.close();
env(delegate::set(alice, bob, {"AccountDomainSet"}));
env.close();
std::string const domain = "example.com";
auto jt = noop(alice);
jt[sfDomain] = strHex(domain);
jt[sfDelegate] = bob.human();
env(jt);
// empty DelegateSet deletes the SLE
env(delegate::set(alice, bob, {}));
env.close();
env(jt, Ter(terNO_DELEGATE_PERMISSION));
}
}
void
@@ -1895,37 +1672,6 @@ class Delegate_test : public beast::unit_test::Suite
env.close();
mpt.set({.account = alice, .flags = tfMPTLock | tfFullyCanonicalSig, .delegate = bob});
}
// field not permitted to exist in granular delegation
{
Env env(*this);
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(100000), alice, bob);
MPTTester mpt(env, alice, {.fund = false});
mpt.create({.flags = tfMPTCanLock});
env.close();
// alice gives granular permission to bob for MPTokenIssuanceLock
env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
env.close();
// Field is not permitted, permitted fields for delegation is defined in
// permissions.macro.
mpt.set(
{.account = alice,
.mutableFlags = 2,
.delegate = bob,
.err = terNO_DELEGATE_PERMISSION});
// Notice: flags not defined in permissions.macro are not permitted for delegation.
// Since preflight will check invalid flag for the tx, it is not reachable.
// If any new flag is defined into the transaction in the future,
// but is not allowed for delegation, the transaction will be rejected with
// terNO_DELEGATE_PERMISSION. The set of permitted flags for delegation is defined in
// permissions.macro.
}
}
void
@@ -2395,62 +2141,6 @@ class Delegate_test : public beast::unit_test::Suite
for (auto const& tx : txRequiredFeatures)
txAmendmentEnabled(tx.first);
}
// Granular permissions also require the amendment for their underlying
// transaction type.
{
for (auto const permission : {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"})
{
Env env(*this, features - featureMPTokensV1);
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(100000), alice, bob);
env.close();
env(delegate::set(alice, bob, {permission}), Ter(temMALFORMED));
}
}
}
void
testGranularSandboxCheckOrder()
{
testcase("Make sure GranularSandbox is checked after transaction-level permission");
using namespace jtx;
Env env(*this);
Account const gw{"gw"};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), gw, alice, bob);
env(fset(gw, asfRequireAuth));
env.close();
env(trust(alice, gw["USD"](50)));
env.close();
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
env.close();
env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
env.close();
// sfQualityOut is a valid TrustSet field, but not permitted in granular template
json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
txJson[sfQualityOut.jsonName] = 100;
env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
// Now Alice grants Bob with transaction level permission
env(delegate::set(gw, bob, {"TrustlineAuthorize", "TrustSet"}));
env.close();
// NOTE: This case is to ensure that if a delegate possesses a
// transaction-level permission (e.g., TrustSet), the granular sandbox must not incorrectly
// block the transaction. The function checkGranularSandbox MUST be called after the
// transaction-level permission check. This test case is to avoid future refactor mistakes,
// modifying the order will fail here.
env(txJson, delegate::As(bob));
}
void
@@ -2503,94 +2193,6 @@ class Delegate_test : public beast::unit_test::Suite
"\n Action: Verify security requirements to interact with Delegation feature");
}
void
testNonDelegableTxWithDelegate(FeatureBitset features)
{
testcase("non-delegable tx with sfDelegate is rejected at preflight");
using namespace jtx;
Env env(*this, features);
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice, bob);
env.close();
// Transactions that are notDelegable and have no granular permissions
// will be rejected with temINVALID at preflight.
// Note: pseudo-transactions (EnableAmendment, SetFee and UNLModify) are also
// notDelegable but are excluded here — passesLocalChecks() blocks them
// before preflight1 is ever reached.
{
// SetRegularKey, SignerListSet, AccountDelete, DelegateSet.
env(regkey(alice, bob), delegate::As(bob), Ter(temINVALID));
env(signers(alice, 1, {{bob, 1}}), delegate::As(bob), Ter(temINVALID));
env(acctdelete(alice, bob), delegate::As(bob), Ter(temINVALID));
env(delegate::set(alice, bob, {"Payment"}), delegate::As(bob), Ter(temINVALID));
// SAV transactions.
{
Vault const vault{env};
auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()});
env(createTx, delegate::As(bob), Ter(temINVALID));
env(vault.set({.owner = alice, .id = keylet.key}),
delegate::As(bob),
Ter(temINVALID));
env(vault.del({.owner = alice, .id = keylet.key}),
delegate::As(bob),
Ter(temINVALID));
env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(1)}),
delegate::As(bob),
Ter(temINVALID));
env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(1)}),
delegate::As(bob),
Ter(temINVALID));
env(vault.clawback({.issuer = alice, .id = keylet.key, .holder = bob}),
delegate::As(bob),
Ter(temINVALID));
}
// Batch transaction: the outer Batch itself is non-delegable.
{
auto const seq = env.seq(alice);
auto const batchFee = batch::calcBatchFee(env, 0, 1);
env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
batch::Inner(pay(alice, bob, XRP(1)), seq + 1),
delegate::As(bob),
Ter(temINVALID));
}
// Lending protocol transactions
{
Vault const vault{env};
auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()});
env(createTx);
env(loanBroker::set(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
env(loanBroker::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
env(loanBroker::coverDeposit(alice, keylet.key, XRP(1)),
delegate::As(bob),
Ter(temINVALID));
env(loanBroker::coverWithdraw(alice, keylet.key, XRP(1)),
delegate::As(bob),
Ter(temINVALID));
env(loanBroker::coverClawback(alice), delegate::As(bob), Ter(temINVALID));
env(loan::set(alice, keylet.key, Number(100)), delegate::As(bob), Ter(temINVALID));
env(loan::manage(alice, keylet.key, 0), delegate::As(bob), Ter(temINVALID));
env(loan::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
env(loan::pay(alice, keylet.key, XRP(1)), delegate::As(bob), Ter(temINVALID));
}
}
// AccountSet is notDelegable at tx level but has granular permissions,
// so sfDelegate passes preflight and is rejected at invokeCheckPermission with
// terNO_DELEGATE_PERMISSION.
{
env(fset(alice, asfDefaultRipple), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
}
}
void
testDelegateUtilsNullptrCheck()
{
@@ -2600,8 +2202,9 @@ class Delegate_test : public beast::unit_test::Suite
STTx const tx{ttPAYMENT, [](STObject&) {}};
BEAST_EXPECT(checkTxPermission(nullptr, tx) == terNO_DELEGATE_PERMISSION);
// getGranularPermission nullptr check
auto const granularPermissions = getGranularPermission(nullptr, ttPAYMENT);
// loadGranularPermission nullptr check
std::unordered_set<GranularPermissionType> granularPermissions;
loadGranularPermission(nullptr, ttPAYMENT, granularPermissions);
BEAST_EXPECT(granularPermissions.empty());
}
@@ -2631,9 +2234,7 @@ class Delegate_test : public beast::unit_test::Suite
testSignForDelegated();
testPermissionValue(all);
testTxRequireFeatures(all);
testGranularSandboxCheckOrder();
testTxDelegableCount();
testNonDelegableTxWithDelegate(all);
testDelegateUtilsNullptrCheck();
}
};

View File

@@ -5,17 +5,21 @@
#include <test/jtx/noop.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/SHAMapStore.h>
#include <xrpld/core/Config.h>
#include <xrpl/basics/ToString.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <cstdint>
#include <memory>
#include <sstream>
#include <vector>
namespace xrpl::test {
@@ -111,6 +115,73 @@ class LedgerMaster_test : public beast::unit_test::Suite
}
}
void
testCompleteLedgerRange(FeatureBitset features)
{
// Note that this test is intentionally very similar to
// SHAMapStore_test::testLedgerGaps, but has a different
// focus.
testcase("Complete Ledger operations");
using namespace test::jtx;
auto const deleteInterval = 8;
Env env{*this, envconfig([](auto cfg) {
return onlineDelete(std::move(cfg), deleteInterval);
})};
auto const alice = Account("alice");
env.fund(XRP(1000), alice);
env.close();
auto& lm = env.app().getLedgerMaster();
LedgerIndex minSeq = 2;
LedgerIndex maxSeq = env.closed()->header().seq;
auto& store = env.app().getSHAMapStore();
BEAST_EXPECT(store.rendezvous());
LedgerIndex lastRotated = store.getLastRotated();
BEAST_EXPECTS(maxSeq == 3, to_string(maxSeq));
BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers());
BEAST_EXPECTS(lastRotated == 3, to_string(lastRotated));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
// Close enough ledgers to rotate a few times
for (int i = 0; i < 24; ++i)
{
for (int t = 0; t < 3; ++t)
{
env(noop(alice));
}
env.close();
BEAST_EXPECT(store.rendezvous());
++maxSeq;
if (maxSeq == lastRotated + deleteInterval)
{
minSeq = lastRotated;
lastRotated = maxSeq;
}
BEAST_EXPECTS(
env.closed()->header().seq == maxSeq, to_string(env.closed()->header().seq));
BEAST_EXPECTS(store.getLastRotated() == lastRotated, to_string(store.getLastRotated()));
std::stringstream expectedRange;
expectedRange << minSeq << "-" << maxSeq;
BEAST_EXPECTS(lm.getCompleteLedgers() == expectedRange.str(), lm.getCompleteLedgers());
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
}
}
public:
void
run() override
@@ -124,6 +195,7 @@ public:
testWithFeats(FeatureBitset features)
{
testTxnIdFromIndex(features);
testCompleteLedgerRange(features);
}
};

View File

@@ -1,7 +1,9 @@
#include <test/jtx/Env.h>
#include <test/jtx/amount.h>
#include <test/jtx/envconfig.h>
#include <test/jtx/noop.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/app/main/NodeStoreScheduler.h>
#include <xrpld/app/misc/SHAMapStore.h>
@@ -9,6 +11,7 @@
#include <xrpld/core/Config.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/config/BasicConfig.h>
@@ -25,12 +28,15 @@
#include <boost/filesystem/path.hpp>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
namespace xrpl::test {
@@ -42,10 +48,7 @@ class SHAMapStore_test : public beast::unit_test::Suite
static auto
onlineDelete(std::unique_ptr<Config> cfg)
{
cfg->ledgerHistory = kDeleteInterval;
auto& section = cfg->section(Sections::kNodeDatabase);
section.set(Keys::kOnlineDelete, std::to_string(kDeleteInterval));
return cfg;
return jtx::onlineDelete(std::move(cfg), kDeleteInterval);
}
static auto
@@ -143,11 +146,11 @@ class SHAMapStore_test : public beast::unit_test::Suite
auto& store = env.app().getSHAMapStore();
int ledgerSeq = 3;
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
BEAST_EXPECT(!store.getLastRotated());
env.close();
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
auto ledger = env.rpc("ledger", "validated");
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++)));
@@ -227,7 +230,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(kDeleteInterval + 4)));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + 3);
lastRotated = store.getLastRotated();
@@ -254,7 +257,7 @@ public:
!getHash(ledgers[i]).empty());
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + lastRotated);
@@ -292,7 +295,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
// The database will always have back to ledger 2,
// regardless of lastRotated.
@@ -307,7 +310,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
BEAST_EXPECT(lastRotated != store.getLastRotated());
@@ -323,7 +326,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, kDeleteInterval + 1, lastRotated);
BEAST_EXPECT(lastRotated != store.getLastRotated());
@@ -362,7 +365,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, ledgerSeq - 2, 2);
BEAST_EXPECT(lastRotated == store.getLastRotated());
@@ -372,7 +375,7 @@ public:
BEAST_EXPECT(!RPC::containsError(canDelete[jss::result]));
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq + (kDeleteInterval / 2));
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, ledgerSeq - 2, 2);
BEAST_EXPECT(store.getLastRotated() == lastRotated);
@@ -385,7 +388,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
@@ -401,7 +404,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
BEAST_EXPECT(store.getLastRotated() == lastRotated);
@@ -413,7 +416,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
@@ -435,7 +438,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
BEAST_EXPECT(store.getLastRotated() == lastRotated);
@@ -447,7 +450,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
@@ -468,7 +471,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
BEAST_EXPECT(store.getLastRotated() == lastRotated);
@@ -480,7 +483,7 @@ public:
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
}
store.rendezvous();
BEAST_EXPECT(store.rendezvous());
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
@@ -603,6 +606,165 @@ public:
BEAST_EXPECT(dbr->getName() == "3");
}
void
testLedgerGaps()
{
// Note that this test is intentionally very similar to
// LedgerMaster_test::testCompleteLedgerRange, but has a different
// focus.
testcase("Wait for ledger gaps to fill in");
using namespace test::jtx;
Env env{*this, envconfig(onlineDelete)};
auto failureMessage = [&](char const* label, auto expected, auto actual) {
std::stringstream ss;
ss << label << ": Expected: " << expected << ", Got: " << actual;
return ss.str();
};
auto const alice = Account("alice");
env.fund(XRP(1000), alice);
env.close();
auto& lm = env.app().getLedgerMaster();
LedgerIndex minSeq = 2;
LedgerIndex maxSeq = env.closed()->header().seq;
auto& store = env.app().getSHAMapStore();
BEAST_EXPECT(store.rendezvous());
LedgerIndex lastRotated = store.getLastRotated();
BEAST_EXPECTS(maxSeq == 3, std::to_string(maxSeq));
BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers());
BEAST_EXPECTS(lastRotated == 3, to_string(lastRotated));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
// Close enough ledgers to rotate a few times
while (maxSeq < 20)
{
for (int t = 0; t < 3; ++t)
{
env(noop(alice));
}
env.close();
BEAST_EXPECT(store.rendezvous());
++maxSeq;
if (maxSeq + 1 == lastRotated + kDeleteInterval)
{
using namespace std::chrono_literals;
// The next ledger will trigger a rotation. Delete the
// current ledger from LedgerMaster.
std::this_thread::sleep_for(100ms);
LedgerIndex const deleteSeq = maxSeq;
{
std::size_t iterations = 30;
while (!lm.haveLedger(deleteSeq) && --iterations > 0)
{
std::this_thread::sleep_for(100ms);
}
BEAST_EXPECTS(iterations > 25, to_string(iterations));
}
lm.clearLedger(deleteSeq);
auto expectedRange = [](auto minSeq, auto deleteSeq, auto maxSeq) {
std::stringstream expectedRange;
expectedRange << minSeq << "-" << (deleteSeq - 1);
if (deleteSeq + 1 == maxSeq)
{
expectedRange << "," << maxSeq;
}
else if (deleteSeq < maxSeq)
{
expectedRange << "," << (deleteSeq + 1) << "-" << maxSeq;
}
return expectedRange.str();
};
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
failureMessage(
"Complete ledgers",
expectedRange(minSeq, deleteSeq, maxSeq),
lm.getCompleteLedgers()));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 1);
// Close another ledger, which will trigger a rotation, but the
// rotation will be stuck until the missing ledger is filled in.
env.close();
// DO NOT CALL rendezvous()! You'll end up with a deadlock.
++maxSeq;
// Nothing has changed
BEAST_EXPECTS(
store.getLastRotated() == lastRotated,
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
failureMessage(
"Complete ledgers",
expectedRange(minSeq, deleteSeq, maxSeq),
lm.getCompleteLedgers()));
// Close 5 more ledgers, waiting one second in between to
// simulate the ledger making progress while online delete waits
// for the missing ledger to be filled in.
// This ensures the healthWait check has time to run and
// detect the gap.
for (int l = 0; l < 5; ++l)
{
env.close();
++maxSeq;
// Nothing has changed
BEAST_EXPECTS(
store.getLastRotated() == lastRotated,
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
failureMessage(
"Complete Ledgers",
expectedRange(minSeq, deleteSeq, maxSeq),
lm.getCompleteLedgers()));
// The Store is "stuck" in healthWait() and won't finish the run() loop until
// it's backfilled
BEAST_EXPECT(!store.rendezvous(100ms));
}
// Put the missing ledger back in LedgerMaster
lm.setLedgerRangePresent(deleteSeq, deleteSeq);
// Wait for the rotation to finish
BEAST_EXPECT(store.rendezvous());
minSeq = lastRotated;
lastRotated = deleteSeq + 1;
}
BEAST_EXPECT(maxSeq != lastRotated + kDeleteInterval);
BEAST_EXPECTS(
env.closed()->header().seq == maxSeq,
failureMessage("maxSeq", maxSeq, env.closed()->header().seq));
BEAST_EXPECTS(
store.getLastRotated() == lastRotated,
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
std::stringstream expectedRange;
expectedRange << minSeq << "-" << maxSeq;
BEAST_EXPECTS(
lm.getCompleteLedgers() == expectedRange.str(),
failureMessage("CompleteLedgers", expectedRange.str(), lm.getCompleteLedgers()));
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
}
}
void
run() override
{
@@ -610,6 +772,7 @@ public:
testAutomatic();
testCanDelete();
testRotate();
testLedgerGaps();
}
};

View File

@@ -51,6 +51,17 @@ envconfig(F&& modfunc, Args&&... args)
return modfunc(envconfig(), std::forward<Args>(args)...);
}
/// @brief adjust config to enable online_delete
///
/// @param cfg config instance to be modified
///
/// @param deleteInterval how many new ledgers should be available before
/// rotating. Defaults to 8, because the standalone minimum is 8.
///
/// @return unique_ptr to Config instance
std::unique_ptr<Config>
onlineDelete(std::unique_ptr<Config> cfg, std::uint32_t deleteInterval = 8);
/// @brief adjust config so no admin ports are enabled
///
/// this is intended for use with envconfig, as in

View File

@@ -7,8 +7,10 @@
#include <xrpl/config/Constants.h>
#include <atomic>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace xrpl::test {
@@ -60,6 +62,15 @@ setupConfigForUnitTests(Config& cfg)
namespace jtx {
std::unique_ptr<Config>
onlineDelete(std::unique_ptr<Config> cfg, std::uint32_t deleteInterval)
{
cfg->ledgerHistory = deleteInterval;
auto& section = cfg->section(Sections::kNodeDatabase);
section.set(Keys::kOnlineDelete, std::to_string(deleteInterval));
return cfg;
}
std::unique_ptr<Config>
noAdmin(std::unique_ptr<Config> cfg)
{

View File

@@ -105,7 +105,10 @@ public:
failedSave(std::uint32_t seq, uint256 const& hash);
std::string
getCompleteLedgers();
getCompleteLedgers() const;
std::size_t
missingFromCompleteLedgerRange(LedgerIndex first, LedgerIndex last) const;
/** Apply held transactions to the open ledger
This is normally called as we close the ledger.
@@ -322,7 +325,7 @@ private:
// A set of transactions to replay during the next close
std::unique_ptr<LedgerReplay> replayData_;
std::recursive_mutex completeLock_;
std::recursive_mutex mutable completeLock_;
RangeSet<std::uint32_t> completeLedgers_;
// Publish thread is running.

View File

@@ -56,6 +56,7 @@
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <boost/icl/concept/interval_associator.hpp>
#include <boost/icl/concept/interval_set.hpp>
#include <xrpl.pb.h>
@@ -1568,12 +1569,25 @@ LedgerMaster::getPublishedLedger()
}
std::string
LedgerMaster::getCompleteLedgers()
LedgerMaster::getCompleteLedgers() const
{
std::scoped_lock const sl(completeLock_);
return to_string(completeLedgers_);
}
std::size_t
LedgerMaster::missingFromCompleteLedgerRange(LedgerIndex first, LedgerIndex last) const
{
RangeSet<LedgerIndex> const target{range(first, last)};
auto const missing = [&target, this] {
std::scoped_lock const sl(completeLock_);
return target - completeLedgers_;
}();
return boost::icl::size(missing);
}
std::optional<NetClock::time_point>
LedgerMaster::getCloseTimeBySeq(LedgerIndex ledgerIndex)
{

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/Ledger.h>
#include <xrpl/nodestore/Manager.h>
#include <chrono>
#include <optional>
namespace xrpl {
@@ -27,8 +28,8 @@ public:
virtual void
start() = 0;
virtual void
rendezvous() const = 0;
[[nodiscard]] virtual bool
rendezvous(std::optional<std::chrono::milliseconds> const& timeout = {}) const = 0;
virtual void
stop() = 0;

View File

@@ -8,6 +8,7 @@
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/scope.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
@@ -233,14 +234,21 @@ SHAMapStoreImp::onLedgerClosed(std::shared_ptr<Ledger const> const& ledger)
cond_.notify_one();
}
void
SHAMapStoreImp::rendezvous() const
bool
SHAMapStoreImp::rendezvous(std::optional<std::chrono::milliseconds> const& timeout) const
{
if (!working_)
return;
return true;
auto notWorking = [&] { return !working_; };
std::unique_lock<std::mutex> lock(mutex_);
rendezvous_.wait(lock, [&] { return !working_; });
if (timeout)
{
return rendezvous_.wait_for(lock, *timeout, notWorking);
}
rendezvous_.wait(lock, notWorking);
return true;
}
int
@@ -311,6 +319,16 @@ SHAMapStoreImp::run()
bool const readyToRotate = validatedSeq >= lastRotated + deleteInterval_ &&
canDelete_ >= lastRotated - 1 && healthWait() == HealthResult::KeepGoing;
{
JLOG(journal_.debug()) << "run: Setting lastGoodValidatedLedger_ to " << validatedSeq;
// Note that this is set after the healthWait() check, so that we
// don't start the rotation until the validated ledger is fully
// processed. It is not guaranteed to be done at this point. It also
// allows the testLedgerGaps unit test to work.
std::unique_lock<std::mutex> const lock(mutex_);
lastGoodValidatedLedger_ = validatedSeq;
}
// will delete up to (not including) lastRotated
if (readyToRotate)
{
@@ -318,7 +336,8 @@ SHAMapStoreImp::run()
<< lastRotated << " deleteInterval " << deleteInterval_
<< " canDelete_ " << canDelete_ << " state "
<< app_.getOPs().strOperatingMode(false) << " age "
<< ledgerMaster_->getValidatedLedgerAge().count() << 's';
<< ledgerMaster_->getValidatedLedgerAge().count()
<< "s. Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
clearPrior(lastRotated);
if (healthWait() == HealthResult::Stopping)
@@ -378,7 +397,9 @@ SHAMapStoreImp::run()
clearCaches(validatedSeq);
});
JLOG(journal_.warn()) << "finished rotation " << validatedSeq;
JLOG(journal_.warn()) << "finished rotation. validatedSeq: " << validatedSeq
<< ", lastRotated: " << lastRotated
<< ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
}
}
}
@@ -622,20 +643,36 @@ SHAMapStoreImp::clearPrior(LedgerIndex lastRotated)
SHAMapStoreImp::HealthResult
SHAMapStoreImp::healthWait()
{
auto index = ledgerMaster_->getValidLedgerIndex();
auto age = ledgerMaster_->getValidatedLedgerAge();
OperatingMode mode = netOPs_->getOperatingMode();
std::unique_lock lock(mutex_);
while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_))
auto const waitTime = recoveryWaitTime_;
auto const ageThreshold = ageThreshold_;
auto numMissing = lastGoodValidatedLedger_ == 0
? 0
: ledgerMaster_->missingFromCompleteLedgerRange(lastGoodValidatedLedger_, index);
while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold || numMissing > 0))
{
lock.unlock();
JLOG(journal_.warn()) << "Waiting " << recoveryWaitTime_.count()
<< "s for node to stabilize. state: "
<< app_.getOPs().strOperatingMode(mode, false) << ". age "
<< age.count() << 's';
std::this_thread::sleep_for(recoveryWaitTime_);
// this value shouldn't change, so grab it while we have the
// lock
auto const lowerBound = lastGoodValidatedLedger_;
ScopeUnlock const unlock(lock);
auto const stream =
mode != OperatingMode::FULL || age > ageThreshold ? journal_.warn() : journal_.info();
JLOG(stream) << "Waiting " << waitTime.count() << "s for node to stabilize. state: "
<< app_.getOPs().strOperatingMode(mode, false) << ". age " << age.count()
<< "s. Missing ledgers: " << numMissing << ". Expect: " << lowerBound << "-"
<< index << ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
std::this_thread::sleep_for(waitTime);
index = ledgerMaster_->getValidLedgerIndex();
age = ledgerMaster_->getValidatedLedgerAge();
mode = netOPs_->getOperatingMode();
lock.lock();
numMissing =
lowerBound == 0 ? 0 : ledgerMaster_->missingFromCompleteLedgerRange(lowerBound, index);
}
return stop_ ? HealthResult::Stopping : HealthResult::KeepGoing;

View File

@@ -72,6 +72,11 @@ private:
std::thread thread_;
bool stop_ = false;
bool healthy_ = true;
// Used to prevent ledger gaps from forming during online deletion. Keeps
// track of the last validated ledger that was processed without gaps. There
// are no guarantees about gaps while online delete is not running. For
// that, use advisory_delete and check for gaps externally.
LedgerIndex lastGoodValidatedLedger_ = 0;
mutable std::condition_variable cond_;
mutable std::condition_variable rendezvous_;
mutable std::mutex mutex_;
@@ -85,11 +90,11 @@ private:
std::uint32_t deleteBatch_ = 100;
std::chrono::milliseconds backOff_{100};
std::chrono::seconds ageThreshold_{60};
/// If the node is out of sync during an online_delete healthWait()
/// call, sleep the thread for this time, and continue checking until
/// recovery.
/// If the node is out of sync, or any recent ledgers are not
/// available during an online_delete healthWait() call, sleep
/// the thread for this time, and continue checking until recovery.
/// See also: "recovery_wait_seconds" in xrpld-example.cfg
std::chrono::seconds recoveryWaitTime_{5};
std::chrono::seconds recoveryWaitTime_{2};
// these do not exist upon SHAMapStore creation, but do exist
// as of run() or before
@@ -145,8 +150,8 @@ public:
void
onLedgerClosed(std::shared_ptr<Ledger const> const& ledger) override;
void
rendezvous() const override;
bool
rendezvous(std::optional<std::chrono::milliseconds> const& timeout = {}) const override;
int
fdRequired() const override;