Compare commits

...

339 Commits

Author SHA1 Message Date
Mayukha Vadari
2c65201bbd remove the callback 2026-06-18 17:35:36 -04:00
Mayukha Vadari
d1a77b453e Merge branch 'develop' of https://github.com/XRPLF/rippled into xrplf/sponsor 2026-06-17 17:50:49 -04:00
Mayukha Vadari
c2ab83c960 fix: Fix UBSan issue (#7554) 2026-06-17 17:50:17 -04:00
yinyiqian1
aac6be218a fix new CI rule switch fallthrough (#7568) 2026-06-17 17:39:12 -04:00
yinyiqian1
14986cc043 Sponsorship should be non-obligated for sponsee (#7552) 2026-06-17 14:08:06 -04:00
solunolab
480676d0bf docs: Fix some comments to improve readability (#7405)
Signed-off-by: solunolab <solunolab@outlook.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-06-17 13:55:00 +00:00
Michael Legleux
f07de6c454 ci: Disable assertions on Release builds (#7443) 2026-06-17 13:54:55 +00:00
Ayaz Salikhov
cb2642be05 build: Add graphviz to Nix images (#7566) 2026-06-17 13:54:46 +00:00
Pratik Mankawde
7e0ff536f5 refactor: Rerevert "Explicitly trim the heap after cache sweeps (#6022)"
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-06-17 13:31:43 +01:00
Pratik Mankawde
044ca7719d release: Bump version to 3.3.0-b0
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-06-17 12:58:01 +01:00
Pratik Mankawde
cccce1c32e Merge remote-tracking branch 'origin/release/3.2.x' into pratik/merge_3.2.x
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-06-17 12:53:02 +01:00
Ayaz Salikhov
5de434436e ci: Make clang-tidy workflow adjustments to stay in sync with Clio (#7563) 2026-06-17 10:02:17 +00:00
Ayaz Salikhov
45ddc1d868 build: Add git-lfs to Nix images (#7561) 2026-06-16 23:13:33 +00:00
Ayaz Salikhov
7b9d55326d build: Add zip to Nix images (#7551) 2026-06-16 17:35:33 +00:00
Ayaz Salikhov
0364e4dc41 docs: Rewrite build environment docs (#7533)
Co-authored-by: Ed Hennis <ed@ripple.com>
2026-06-16 13:24:12 +00:00
Ayaz Salikhov
3c43f4614f release: Bump version to 3.2.0 2026-06-15 19:46:56 -04:00
dependabot[bot]
6b63f0ff61 ci: [DEPENDABOT] bump codecov/codecov-action from 6.0.1 to 7.0.0 (#7426)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 19:46:49 -04:00
Oleksandr
4fc781ef93 Merge remote-tracking branch 'ripp/develop' into xrplf/sponsor 2026-06-15 18:37:38 -04:00
Oleksandr
e52113956e clang-tidy 2026-06-15 18:21:33 -04:00
Bart
0ac8e6cf1e release: Bump version to 3.2.0-rc6 2026-06-15 22:24:03 +01:00
Vito Tumas
ed5f13481a fix: Disable transaction invariants 2026-06-15 22:24:03 +01:00
Vito Tumas
781ef175c9 perf: Dispatch "hasInvalidAmount()" on type tag instead of dynamic_cast 2026-06-15 22:24:03 +01:00
Ed Hennis
e5785c4fcb fix: Fix Number comparison operator 2026-06-15 22:24:02 +01:00
Michael Legleux
96d0563ea6 fix: Adjust xrpld systemd service 2026-06-15 22:24:02 +01:00
Bart
61dae6f792 release: Bump version to 3.2.0-rc5 2026-06-15 22:24:02 +01:00
yinyiqian1
fded06652a fix: Add zero NFT Offer ID check for NFTokenCancelOffer 2026-06-15 22:24:02 +01:00
Valentin Balaschenko
e833e8884d refactor: Revert "Explicitly trim the heap after cache sweeps (#6022)" 2026-06-15 22:24:02 +01:00
Michael Legleux
8e3eabc398 refactor: Remove auto-update script and update RPM version
* refactor: Update RPM version scheme; remove auto-update script; service hardening

- **RPM version scheme**: pre-releases now use `~` in the `Version` field instead of the `0.<release>.<suffix>` `Release`-field hack. Matches Debian's `~` convention, so RPM and DEB version strings are symmetric. Requires rpm ≥ 4.10 (RHEL 9 ships 4.17).

  Before/after for a pre-release build:
  ```
  # before
  xrpld-3.2.0-0.1.rc3+202606011647.d4cb68d5.el9.x86_64.rpm

  # after (symmetric with DEB)
  xrpld-3.2.0~rc2+202606010139.7679a310-1.el9.x86_64.rpm
  xrpld_3.2.0~rc2+202606010139.7679a310-1_amd64.deb
  ```
- **Auto-update removed**: `update-xrpld`, `update-xrpld.service`, and `update-xrpld.timer` deleted. The `50-xrpld.preset` `disable` line for the timer is dropped too.
- **Service hardening** (two new `[Service]` directives in `xrpld.service`):
  - `CapabilityBoundingSet=CAP_NET_BIND_SERVICE` — drops every Linux capability except `CAP_NET_BIND_SERVICE`, capping the privilege ceiling to least-privilege while still letting operators bind ports <1024 (e.g. WS/HTTPS on 443).
  - `SystemCallArchitectures=native` — restricts the service to the native syscall ABI, blocking alternate-ABI (32-bit/x32) syscalls used to evade seccomp filtering.

- [ ] Build RPM from a pre-release version (e.g. `3.2.0-b1`) and confirm `rpm -qi` shows `Version: 3.2.0~b1`, `Release: 1`
- [ ] Confirm `3.2.0~b1` sorts before `3.2.0` via `rpmvercmp`
- [ ] Install package and confirm no `update-xrpld*` units appear in `systemctl list-unit-files`
- [ ] Confirm `systemctl show xrpld` reflects the new `CapabilityBoundingSet` and `SystemCallArchitectures`

* fix: Track tmpfiles-created directories in RPM %files as %ghost
2026-06-15 22:24:02 +01:00
Sergey Kuznetsov
47b06ecd17 refactor: Use rocksdb includes only when it is available 2026-06-15 22:23:54 +01:00
Bart
5a25c9188b release: Bump version to 3.2.0-rc4 2026-06-15 22:23:53 +01:00
Bart
82ee5b7556 refactor: Handle int and uint API versions separately 2026-06-15 22:23:38 +01:00
Pratik Mankawde
f98c251011 refactor: Improve tracking of book (un)subscriptions 2026-06-15 22:23:38 +01:00
Sergey Kuznetsov
e29dc474b3 refactor: Improve payment channel closing and returned error codes 2026-06-15 22:23:28 +01:00
Pratik Mankawde
2728e11809 fix: Set request size limits and differential pricing for get-object-by-hash calls 2026-06-15 22:23:28 +01:00
Jingchen
9650fe8a6e refactor: Use explicit types to help compiler 2026-06-15 22:22:53 +01:00
Pratik Mankawde
2df96b1550 fix: Silence UBSan diagnostics in the ubsan build config (#7531)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 19:25:37 +00:00
Ayaz Salikhov
fe4c8ae82a build: Add ClangBuildAnalyzer to Nix (#7538)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-06-15 19:04:33 +00:00
Oleksandr
f650d52ada Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-06-15 12:54:37 -04:00
Zhiyuan Wang
b34aa84e5a fix: Check Fee-Free Division by Zero in AMMWithdraw singleWithdrawEPrice (#6989) 2026-06-15 15:31:22 +00:00
Bart
f5985e73ec fix: Always charge peer on strand (#7422)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-06-15 14:55:56 +00:00
Sergey Kuznetsov
4387aac1a5 chore: Remove conan patch in nix (#7534) 2026-06-15 14:55:43 +00:00
Pratik Mankawde
df395d6851 test: Add null check unit test for Oracle::aggregatePrice (#7306)
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-06-11 18:05:36 +00:00
Ayaz Salikhov
8e618d68cd ci: Patch conan recipe for Nix to be able to use on macOS (#7532) 2026-06-11 17:36:33 +00:00
Ayaz Salikhov
cee157485e ci: Run sanitizers on release builds too (#7527) 2026-06-11 12:59:22 +00:00
Oleksandr
64159eb040 Revert clang-tidy 2026-06-03 22:40:10 -04:00
Oleksandr
942a950e93 Codegen update 2026-06-03 19:48:20 -04:00
Oleksandr
d70eca3d7d Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-06-03 19:47:19 -04:00
Oleksandr
7e5ac6f8cb Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-06-03 19:31:46 -04:00
Oleksandr
03fb6d1e0c Codegen update 2026-06-03 18:29:55 -04:00
Oleksandr
9ef1949309 Merge remote-tracking branch 'ripple/develop' into xrplf/sponsor 2026-05-28 15:19:29 -04:00
Oleksandr
595b612589 Merge fixes 2026-05-28 01:54:20 -04:00
Oleksandr
d561f0d84d Merge remote-tracking branch 'tequ/sponsor' into spns5 2026-05-26 17:06:36 -04:00
Oleksandr
3859fcad73 clang-tidy fixes 2026-05-26 17:06:36 -04:00
Oleksandr
8d905ac21b Merge remote-tracking branch 'ripp/develop' into spns5 2026-05-26 17:06:36 -04:00
Oleksandr
0997b92de3 Merge fixes 2026-05-26 17:04:16 -04:00
Oleksandr
9e5b7281cf Merge remote-tracking branch 'ripple/develop' into spns5 2026-05-26 17:00:29 -04:00
Oleksandr
0bfdfa73ff Merge fixes 2026-05-26 15:50:54 -04:00
Oleksandr
39e819927f Merge remote-tracking branch 'ripple/develop' into spns5 2026-05-26 15:49:42 -04:00
Oleksandr
08357b196f sponsor AccountID -> sponsorSle 2026-05-26 15:42:03 -04:00
tequ
bb4c443fe5 fix ReserveCount Inflation via Co-Signed + Pre-Funded Interaction
fix #6864
2026-05-26 15:39:50 -04:00
tequ
a669099f21 fix 2026-05-26 15:39:50 -04:00
tequ
7624c18c76 fix: XChainAddAccountCreateAttestation redirects relayer sponsorship to door-owned claim objects 2026-05-26 15:39:50 -04:00
tequ
ab7bbac8f9 Merge remote-tracking branch 'oleks-rip/dev_rename_merge' into sponsor 2026-05-26 15:39:49 -04:00
Oleksandr
960aaec91e merge fixes 2026-05-26 15:39:46 -04:00
Oleksandr
f042cd8e25 Merge remote-tracking branch 'ripple/develop' into dev_rename_merge 2026-05-26 15:37:34 -04:00
tequ
9d4c4968ad fix tidy error 2026-05-26 14:47:54 -04:00
Oleksandr
b76652bdf4 XRPL_ASSERT -> Throw 2026-05-26 14:47:54 -04:00
tequ
405b314842 fix clang-tidy error 2026-05-26 14:47:54 -04:00
tequ
77ebc2921e clang-format 2026-05-26 14:47:54 -04:00
tequ
c72fd59539 clang-tidy 2026-05-26 14:47:54 -04:00
tequ
6f4365557d Merge branch 'develop' into sponsor 2026-05-26 14:47:54 -04:00
Oleksandr
d792e03468 adjustOwnerCountObj, more checks, some fixes 2026-05-26 14:47:53 -04:00
Oleksandr
2b4ce0fc21 Merge fixes 2026-05-26 14:47:53 -04:00
Oleksandr
eb88c0ef6b Fix clang-format, some refactoring 2026-05-26 14:46:41 -04:00
tequ
50d5e2d996 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-05-26 14:46:41 -04:00
tequ
bca6d8e515 fix not to payback ReserveCount 2026-05-26 14:46:41 -04:00
tequ
86c90c8e5c clang-tidy 2026-05-26 14:46:41 -04:00
tequ
0c683bfd66 levelization 2026-05-26 14:46:41 -04:00
tequ
33be9abbef clang-format 2026-05-26 14:46:41 -04:00
tequ
aa70d6051b Merge remote-tracking branch 'upstream/develop' into sponsor 2026-05-26 14:46:41 -04:00
tequ
d057f07d0e fix: setSponsorFieldU32 silently clamps to 0 on underflow instead of failing 2026-05-26 14:40:07 -04:00
tequ
688e568e39 fix: getLedgerEntryOwner missing ltLOAN_BROKER and ltLOAN prevents object sponsorship 2026-05-26 14:40:07 -04:00
tequ
3ad93af34d fix: Sponsor's MaxFee cap is bypassed in reset() path, allowing sponsee to drain entire pre-funded FeeAmount in a single tec-failing transaction 2026-05-26 14:40:07 -04:00
Oleksandr
c5941d6b14 Unify accountReserve, ownerCount, ownerReserve 2026-05-26 14:40:07 -04:00
Oleksandr
ee8d9ca73e More universal adjustSponsorOwnerCountHlp 2026-05-26 14:40:07 -04:00
Oleksandr
462a0b2338 Read/const_ref 2026-05-26 14:40:06 -04:00
Oleksandr
9b4ab67864 Fix delegable test 2026-05-26 14:40:06 -04:00
Oleksandr
84bffad2bf merge_fix 2026-05-26 14:40:06 -04:00
tequ
b153213e83 run pre-commit 2026-04-28 17:20:00 +09:00
tequ
c12eff6646 apply tidy diff 2026-04-28 17:09:57 +09:00
tequ
fbff1c065c levelization 2026-04-28 17:04:14 +09:00
tequ
e8deaa12d8 pre-commit run 2026-04-28 16:55:07 +09:00
tequ
6ee071c5b0 fix: An incorrect available XRP balance check in AMM deposit allows reserve-locked funds to be used as adding liquidity 2026-04-28 15:57:49 +09:00
tequ
381094498d Merge remote-tracking branch 'upstream/develop' into sponsor 2026-04-28 09:49:52 +09:00
tequ
c072b125a0 fix: No invariant verifying sfSponsor field on objects matches sponsoring/sponsored count deltas #6897 2026-04-21 18:59:36 +09:00
tequ
b2b2babe1e fix: PermissionedDomain invariant skips credential validation for SponsorshipTransfer #6902 2026-04-21 18:28:09 +09:00
tequ
bb27479686 fix: TrustSet free trust line check uses sponsor's ownerCount, skipping sponsor reserve validation #6901 2026-04-21 17:51:50 +09:00
tequ
278e25d8ad fix: Preclaim XRP reserve overestimate for sponsored checks #6899 2026-04-21 17:25:33 +09:00
tequ
773acf0af7 fix: SponsoringAccountCount has no overflow protection #6898 2026-04-21 17:20:22 +09:00
tequ
7639bd9061 fix: MPTokenIssuanceDestroy reads sponsor from erased SLE #6895 2026-04-21 16:27:33 +09:00
tequ
207a33d3da fix: SponsorshipSet Update FeeAmount Lacks Reserve Floor Check 2026-04-21 15:56:28 +09:00
tequ
52e7cdc24d fix: Sponsored account creation double-counts base reserve #6894 2026-04-21 13:48:07 +09:00
tequ
dd82977e6c Merge remote-tracking branch 'upstream/develop' into sponsor 2026-04-13 18:08:30 +09:00
tequ
31bc25973a fix SponsorshipSet Update FeeAmount Lacks Reserve Floor Check 2026-04-13 15:56:29 +09:00
tequ
021eaa81bf fix Co-Signed Sponsoring Bypasses Sponsor Balance Check 2026-04-13 15:30:33 +09:00
tequ
84e84fbadf fix Sponsor able to provide sponsorship with just base reserve 2026-04-13 14:03:37 +09:00
tequ
b6b317597f add test for insufficient balance to sponsor fee 2026-04-10 21:43:35 +09:00
tequ
525adf122f fix reset() Does Not makeFieldAbsent for Zero FeeAmount 2026-04-10 18:28:28 +09:00
tequ
16e4f24226 fix XRPL_ASSERT(false) to UNREACHABLE 2026-04-10 17:59:25 +09:00
tequ
3df1e668da fix Unchecked std::optional Dereference in SponsorshipSet Create Path 2026-04-10 17:55:40 +09:00
tequ
9264136b36 fix deleteAMMTrustLine Reads Sponsor Fields After They Are Erased 2026-04-10 17:41:33 +09:00
tequ
6bd2e911eb fix Wrong Account in Sponsorship Keylet Lookup Causes Reserve Count
Leakage
2026-04-10 17:07:36 +09:00
tequ
828b4753fe fix Wrong Account Passed to getLedgerEntryOwner in doApply 2026-04-10 15:57:15 +09:00
tequ
d524670bcd fix SponsorshipEnd Bypass 2026-04-10 15:54:16 +09:00
tequ
25de0ce1b1 fix comment 2026-04-10 14:21:05 +09:00
tequ
4026af175b format 2026-04-10 14:13:55 +09:00
tequ
a115d73c56 return tecHAS_OBLIGATIONS for any conditions when deleting account linked to ltSponsorship 2026-04-10 14:13:25 +09:00
tequ
71003699dc Add test for no SponsorFlag with valid sponsor 2026-04-10 13:45:14 +09:00
tequ
831f99862d clang-tidy 2026-04-10 05:51:22 +09:00
tequ
4d2945323d fix AccountObjects_test.cpp error 2026-04-10 04:21:11 +09:00
tequ
700a528a25 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-04-10 03:38:07 +09:00
tequ
07d0887efb fix path 2026-04-09 00:46:20 +09:00
tequ
3ea0067e6a clang-format 2026-04-08 12:03:12 +09:00
tequ
5a195d2585 fix path 2026-04-08 11:55:35 +09:00
tequ
3f4e88c300 Merge remote-tracking branch 'xrplf/develop' into sponsor 2026-04-08 11:47:16 +09:00
tequ
7c3ca98bd5 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-31 14:22:49 +09:00
tequ
2505fa86f7 fix 2026-03-30 17:52:36 +09:00
tequ
d0d4f1d153 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-30 17:25:14 +09:00
tequ
41429dcc99 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-25 11:59:44 +09:00
tequ
aacb8e4a68 return tem error when SponsorReserve flag is set in Batch OuterTxn 2026-03-23 13:31:36 +09:00
tequ
98257d3b96 Fix SponsorshipSet targeting Pseudo Accounts to return an error 2026-03-23 13:07:28 +09:00
tequ
afcc3c6464 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-23 12:46:27 +09:00
tequ
fd393685dd fix L20 2026-03-23 12:46:17 +09:00
tequ
de64bc7855 fix L18 2026-03-23 12:34:33 +09:00
tequ
1adaeac8a7 fix L14 2026-03-23 12:14:44 +09:00
tequ
7ddda1b1e6 fix L5 2026-03-23 12:01:30 +09:00
tequ
5280a185d0 fix L4 2026-03-23 11:59:46 +09:00
tequ
64f699a974 fix M17 2026-03-23 11:14:41 +09:00
tequ
968493f43b fix M16 2026-03-23 11:10:45 +09:00
tequ
22bfc5e743 fix M15 2026-03-23 11:02:59 +09:00
tequ
2f0adfae3b fix M11 2026-03-23 10:55:42 +09:00
tequ
53bdfb2bc7 Add comment to clarify that the door account should not have a sponsor in the reserve check 2026-03-23 10:28:27 +09:00
tequ
b695b4bf9b Add comments for PreFunded sponsor reserve checks on TrustSet 2026-03-23 10:27:18 +09:00
tequ
6d7ffcbb91 fix M7 2026-03-19 15:39:55 +09:00
tequ
d1346fa3f6 fix M6 2026-03-19 15:14:14 +09:00
tequ
d6803868a1 fix M5 2026-03-19 15:00:09 +09:00
tequ
9a6432dd66 fix M3 2026-03-19 14:50:09 +09:00
tequ
f8a8c2301c fix M2 2026-03-19 14:33:55 +09:00
tequ
e40e15a866 fix C2, C4 2026-03-19 14:01:11 +09:00
tequ
6555001cd9 make sponsor helpers inline 2026-03-19 11:50:55 +09:00
tequ
f13945eff9 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-19 11:10:44 +09:00
tequ
b32ff18c21 refactor getFeePayer() 2026-03-18 00:19:21 +09:00
tequ
8baec5634c pre-commit run --all-files 2026-03-18 00:03:31 +09:00
tequ
37c95ead69 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-18 00:02:57 +09:00
tequ
234d2e5414 Refactor ownerCount and calculateReserve to use SLE::const_ref 2026-03-17 21:38:25 +09:00
tequ
024ab07eb4 minor fixes 2026-03-17 21:35:08 +09:00
tequ
d95b6e41e5 Refactor variable declarations in xrpLiquid 2026-03-17 21:15:41 +09:00
tequ
8d75f3bdc6 remove asfDisallowIncomingSponsor 2026-03-17 21:13:35 +09:00
tequ
eb7e01de7e Remove unused tx parameter from removeEmptyHolding() 2026-03-17 21:04:40 +09:00
tequ
c79814a037 remove unused Sponsor.h 2026-03-17 20:39:57 +09:00
tequ
15be71469d remove unnecessary reserveCount check 2026-03-17 20:35:50 +09:00
tequ
c9666d7b45 refactor adjustOwnerCount() to use adjustSponsorOwnerCountHlp() 2026-03-17 20:28:01 +09:00
tequ
839ba17dae Refactor: sponsorship-related helpers 2026-03-17 20:11:49 +09:00
tequ
85e1f2b16c Rename [sponsor|sponsee]Acc,[sponsor|sponsee]Account to [sponsor|sponsee]AccountID for clarity 2026-03-17 18:32:30 +09:00
tequ
8a05e3d93b remove unnecessary #endif in Sponsor.h and sponsor.h 2026-03-17 18:14:48 +09:00
tequ
8c8ecffe1f remove tx parameter from adjustOwnerCount() 2026-03-17 18:14:24 +09:00
tequ
b9f1c97518 format SetTrust.cpp 2026-03-17 17:43:35 +09:00
tequ
d1eb22b403 fix transactions.macro format 2026-03-17 17:38:04 +09:00
tequ
fb86735800 use pragma once 2026-03-17 17:26:13 +09:00
tequ
53f407e1bd Merge remote-tracking branch 'upstream/develop' into sponsor 2026-03-17 17:22:54 +09:00
tequ
99618877e4 clang-format 2026-02-23 23:55:25 +09:00
tequ
062ece321b Merge remote-tracking branch 'upstream/develop' into sponsor 2026-02-23 23:20:21 +09:00
tequ
1ad6dd1846 Add tests for deleting Sponsor Account with ltSponsorship 2026-02-23 11:10:46 +09:00
tequ
4a91351bcd Add sfSponsee to ttSponsorshipTransfer 2026-02-22 17:21:04 +09:00
tequ
f86b255e73 Add SponsorshipEnd/Create/Reassign flags for SponsorshipTransfer 2026-02-22 02:41:01 +09:00
tequ
178bb8f7e9 Allow zero value for ReserveCount, FeeAmount, MaxFee 2026-02-22 00:01:56 +09:00
tequ
293394fbc6 audit 14 2026-02-18 17:22:23 +09:00
tequ
dcfcb1f3fd audit 13 2026-02-18 16:33:32 +09:00
tequ
2fd8e1be3c audit 12 2026-02-18 15:55:10 +09:00
tequ
fe5fab00ed audit 11 2026-02-18 15:18:17 +09:00
tequ
9cde57d284 audit 10 2026-02-18 15:09:12 +09:00
tequ
9859ba85a8 audit 9 2026-02-18 15:01:35 +09:00
tequ
f6d79f74d6 audit 8 2026-02-18 14:53:21 +09:00
tequ
bf4fa37937 audit 7 2026-02-18 14:48:51 +09:00
tequ
686f945909 audit 6 2026-02-18 14:46:03 +09:00
tequ
c1e1be510f audit 5 2026-02-18 14:39:27 +09:00
tequ
d1d613da27 audit 4 2026-02-18 14:19:40 +09:00
tequ
392a913631 audit 3 2026-02-18 14:09:11 +09:00
tequ
446681dc3d Merge remote-tracking branch 'upstream/develop' into sponsor 2026-02-18 13:39:57 +09:00
tequ
e1aee43359 address review 2026-02-03 14:45:55 +09:00
tequ
fe075470f8 address review 2026-02-02 18:57:04 +09:00
tequ
49c52695cb sfSponsorAccount -> sfSponsor/sfCounterpartySponsor 2026-02-02 12:51:35 +09:00
tequ
37f4ea94cb Add tests for Loan 2026-02-02 12:30:06 +09:00
tequ
f8209087a3 Add tests for LoanBroker transactions 2026-02-01 20:38:37 +09:00
tequ
d390bc7a87 clang-format 2026-01-31 01:33:36 +09:00
tequ
4e87de7303 add test for Sponsored trustline 2026-01-31 01:31:37 +09:00
tequ
8d24afe59d Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-30 23:29:56 +09:00
tequ
527d7bbede fix to return error if FeeAmount > Balance on SponsorshipSet 2026-01-30 23:24:50 +09:00
tequ
4a205eb9d8 add checks to accountReserve and fix sponsored owner count for xrpLiquid 2026-01-30 22:23:45 +09:00
tequ
0a410024a5 refactor SponsorshipSet flag checks 2026-01-30 22:22:09 +09:00
tequ
8f8fc03800 Add InvariantChecks for if OwnerCount < SponsoredOwnerCount 2026-01-30 21:21:38 +09:00
tequ
40cf5993c1 Add sanity check for sponsoringAccountCount = 0 2026-01-30 20:36:43 +09:00
tequ
7cdaa24e63 Fixed the minimum amount for creating a Sponsored Account. 2026-01-30 20:27:46 +09:00
tequ
175a2dfd28 chang sfFeeAmount to soeOPTIONAL 2026-01-30 20:03:24 +09:00
tequ
4cd8d0d178 fix simulate test 2026-01-30 19:51:07 +09:00
tequ
5155a94015 Add sponsor tests for Batch innerTxn 2026-01-30 17:20:15 +09:00
tequ
c0de722cfa sfHigh/LowSponsorAccount -> High/LowSponsor 2026-01-30 12:32:48 +09:00
tequ
d7ab1b48e1 Separate test suite 2026-01-30 11:43:45 +09:00
tequ
65768237cd Payback ReserveCount 2026-01-30 11:36:56 +09:00
tequ
5f385e0f9b update to use sfSponsorFlags 2026-01-30 10:36:48 +09:00
tequ
ab1de456cc add comment 2026-01-29 13:52:19 +09:00
tequ
8be44c7fc4 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-29 11:22:42 +09:00
tequ
87718bdb3b remove unused HashPrefix 2026-01-29 10:58:06 +09:00
tequ
88e870b1c6 change sfSponsor to STAccount, Sponsor flags as global flags 2026-01-27 12:38:01 +09:00
tequ
ce8049a17f fix parseSponsorship 2026-01-27 11:34:08 +09:00
tequ
990627fc9e Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-27 10:38:24 +09:00
tequ
1823d70b21 refactor Oracle Reserve calculation 2026-01-27 10:34:15 +09:00
tequ
f333dd1b2e add signature existence check, consume ReserveCount if pre-funded sponsoring 2026-01-27 10:33:52 +09:00
tequ
204138fb0e Revert the use of calculateReserve in xrpLiquid 2026-01-15 18:48:35 +09:00
tequ
52ae31bb17 Merge commit 'c9458b72cab68d3cbbf533cc87d14309c2eb93b7' into sponsor 2026-01-15 16:34:24 +09:00
tequ
c3e5bcfafd address review (DeleteAccount) 2026-01-15 15:53:29 +09:00
tequ
3568df43c4 add calculateReserve helper 2026-01-15 15:01:55 +09:00
tequ
c321e1070d address reviews 2026-01-15 14:51:05 +09:00
tequ
9e65dba253 fix redundant co-signed sponsor check 2026-01-15 14:32:47 +09:00
tequ
5b91d815d1 address revew (std::optional + std::shared_ptr<SLE>) 2026-01-15 14:29:37 +09:00
tequ
f885f02ede address spec changes (sfReserveCount, sfFeeAmount) 2026-01-14 19:28:10 +09:00
tequ
1bc5a1bb43 address spec changes (payment flags/ iou amount) 2026-01-14 12:49:49 +09:00
tequ
ec05613472 fix typo 2026-01-09 16:45:12 +09:00
tequ
f88e964dac allow sponsee on cspell 2026-01-09 16:40:15 +09:00
tequ
ffb538242a Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-09 16:38:44 +09:00
tequ
1e8f6ce1b5 address reviews 2026-01-09 16:31:22 +09:00
tequ
82b146bf03 fix ripple -> xrpl 2026-01-07 17:16:50 +09:00
tequ
0791ca164a fix template exception 2026-01-07 16:42:18 +09:00
tequ
dc325a93e7 Merge branch 'develop' into sponsor 2026-01-07 12:35:47 +09:00
tequ
edddf3fe4b address review 2026-01-07 12:34:51 +09:00
tequ
cf03c4cb8f change Sponsor related fields to soeDEFAULT 2026-01-07 12:31:01 +09:00
tequ
2813fea294 address review 2026-01-07 11:39:56 +09:00
tequ
163a2acbf5 add sfPreviousTxnID and sfPreviousTxnLgrSeq to ltSponsorship 2026-01-07 11:27:10 +09:00
tequ
198f3f83db remove unused insSponsorCoSigning args 2026-01-07 11:14:01 +09:00
tequ
0f36de8ef5 Merge remote-tracking branch 'upstream/develop' into sponsor 2026-01-07 10:58:21 +09:00
tequ
ea29fe5ede Merge remote-tracking branch 'upstream/develop' into sponsor 2025-12-11 10:31:48 +09:00
tequ
f5e2195bbb address reviews 2025-12-11 10:31:30 +09:00
tequ
e49d338cd8 address SponsorshipSet reviews 2025-11-26 17:48:35 +09:00
tequ
d96998ce47 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-26 17:20:19 +09:00
tequ
a9629ae325 address reviews 2025-11-21 17:44:05 +09:00
tequ
7f0a940a43 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-21 17:18:20 +09:00
tequ
c38cb902ad Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-21 17:17:48 +09:00
tequ
b5db59644a update Sponsored VaultCreate test 2025-11-17 18:29:22 +09:00
tequ
9d1b5eb373 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-17 18:19:30 +09:00
tequ
af9d91bc72 add tests fro SponsorshipTransfer 2025-11-10 22:22:32 +09:00
tequ
3f9673510a fix comment for LCOV 2025-11-10 21:50:31 +09:00
tequ
a49cb91c73 add tests 2025-11-10 12:11:42 +09:00
tequ
fbf403aaa4 add tests for sponsor field 2025-11-10 00:55:39 +09:00
tequ
e03c7a9c96 add sponsor transfer test 2025-11-10 00:40:15 +09:00
tequ
cd62f7f4cf remove copyright 2025-11-06 21:08:51 +09:00
tequ
297083e27b fix: specify type for adjust variable in SetOracle transaction processing 2025-11-06 18:36:17 +09:00
tequ
13145f6db2 fix header name 2025-11-06 18:34:24 +09:00
tequ
3e6d6c6f80 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-11-06 18:08:13 +09:00
tequ
b4188a887a address reviews 2025-11-06 17:53:07 +09:00
tequ
774917db22 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-10-31 11:24:47 +09:00
tequ
965ecd3f27 Add sponsored outer batch 2025-10-28 21:07:20 +09:00
tequ
d0bcca6bf1 Fix the behavior of co-sign + pre-fund 2025-10-28 17:18:38 +09:00
tequ
4fe7e87b4f Merge remote-tracking branch 'upstream/develop' into sponsor 2025-10-17 19:21:24 +09:00
tequ
2337d340e5 Allow delegation for Sponsorship transactions 2025-10-17 18:56:46 +09:00
tequ
5d597e3b69 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-10-16 22:08:42 +09:00
tequ
d8dc000488 add prefunded sponsor tests 2025-10-09 19:12:14 +09:00
tequ
a65cf6e07d add tests for sponsor flags 2025-10-08 14:09:02 +09:00
tequ
7f922c4919 reserve co-signing 2025-10-08 02:50:08 +09:00
tequ
754f597078 Support sfSponsorSignature autofilling in Simulate RPC 2025-10-07 16:02:02 +09:00
tequ
32cc7825f2 Merge branch 'refactor-simulate-autofill' into sponsor 2025-10-07 15:30:48 +09:00
tequ
a6b0efff87 typo 2025-10-07 11:19:31 +09:00
tequ
28c5cadbea fix transactions.macro format 2025-10-07 10:45:30 +09:00
tequ
9df0ad64d1 Merge branch 'sponsor-new-signing' into sponsor 2025-10-07 09:24:58 +09:00
tequ
1441abcf01 use sfSponsorSignature 2025-10-07 09:01:45 +09:00
Ed Hennis
8d32b0f856 Add jtx, STObject, and RPC support for sig object fields 2025-10-07 08:41:43 +09:00
tequ
d59772657e refactor: signature autofilling for Simulate RPC 2025-10-04 14:37:06 +09:00
Ed Hennis
bc71d9c138 Add support for extra transaction signature validation
- Restructures `STTx` signature checking code to be able to handle
  a `sigObject`, which may be the full transaction, or may be an object
  field containing a separate signature. Either way, the `sigObject` can
  be a single- or multi-sign signature.
- This is distinct from 550f90a75e (#5594), which changed the check in
  Transactor, which validates whether a given account is allowed to sign
  for the given transaction. This cryptographically checks the signature
  validity.
2025-10-04 13:22:56 +09:00
tequ
101b70f0e8 add assert when adding/removing sponsor field to LedgerEntry 2025-10-03 15:07:46 +09:00
tequ
ee385e9d3b fix bad merge 2025-10-03 15:04:33 +09:00
tequ
4126d53978 Merge remote-tracking branch 'origin/develop' into sponsor 2025-10-03 09:39:47 +09:00
tequ
59c3d5bddc add insufficient reserve check for xchain, vault 2025-10-02 23:37:44 +09:00
tequ
bc1ef1e839 add tests for XChainBridge 2025-10-02 18:29:12 +09:00
tequ
5b64ff0dd6 address 5594, 5592 changes 2025-09-30 18:30:38 +09:00
tequ
1ee250881b Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-30 12:06:09 +09:00
tequ
75aaeb5aae Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-26 18:26:40 +09:00
tequ
b09666210d add InvariantChecks for pseudo-account 2025-09-26 18:25:49 +09:00
tequ
dbbfc1354c test for Vault 2025-09-26 18:21:24 +09:00
tequ
e82c300de7 test Sponsor Reserve checks for AMM 2025-09-26 10:15:00 +09:00
tequ
d5a1314c47 test Sponsor Reserve checks for TrustSet 2025-09-24 19:05:22 +09:00
tequ
699297f01a test Sponsor Reserve checks for SignerListSet 2025-09-24 18:59:16 +09:00
tequ
71b81b743e test Sponsor Reserve checks for PayChan, PermissionedDomain, Oracle 2025-09-24 17:35:23 +09:00
tequ
ec11763def test Sponsor Reserve checks for NFToken 2025-09-24 17:12:02 +09:00
tequ
cc4b07dbd6 test Sponsor Reserve checks for DID, Escrow 2025-09-24 16:18:09 +09:00
tequ
b78885d98c test Sponsor Reserve checks for DepositPreauth 2025-09-24 15:59:06 +09:00
tequ
0c93bd6f08 test Sponsor Reserve checks for DelegateSet 2025-09-24 15:57:49 +09:00
tequ
9694de92da test Sponsor Reserve checks for Credentials 2025-09-24 15:54:48 +09:00
tequ
c4b798e8ed test Sponsor Reserve checks for TicketCreate 2025-09-24 15:41:58 +09:00
tequ
350130911a test Sponsor Reserve checks for OfferCreate 2025-09-24 15:34:55 +09:00
tequ
ab4ac64c92 test Sponsor Reserve checks for Checks 2025-09-24 15:05:31 +09:00
tequ
aeaf679deb Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-24 13:42:57 +09:00
tequ
ea72198a1d Transfer Trustline Sponsorship 2025-09-24 13:42:05 +09:00
tequ
6eb453f07d Merge branch 'develop' into sponsor 2025-09-20 23:08:53 +09:00
tequ
d4947386d7 Fully Support NFTokenMint/Burn 2025-09-20 17:03:20 +09:00
tequ
fed56b2eeb address SponsorAccount/Sponsee field changes 2025-09-20 14:29:33 +09:00
tequ
f7e1d4bbb9 add AccountTx tests for Sponsorship 2025-09-20 12:20:29 +09:00
tequ
613fe48d54 update account_objects for sponsorship 2025-09-20 11:44:47 +09:00
tequ
5dc63b64eb fix SponsorshipTransfer 2025-09-20 10:34:46 +09:00
tequ
cc5e063438 add Credential sponsor test 2025-09-19 21:25:46 +09:00
tequ
453fd23512 add Sponsor Oracle 2025-09-19 21:12:34 +09:00
tequ
89af81745b add tests 2025-09-19 01:36:45 +09:00
tequ
a43ae9a3ce add InvariantCheck for sponsor count 2025-09-18 18:49:53 +09:00
tequ
2afe5707ab High/Low SponsorAccount 2025-09-18 16:32:50 +09:00
tequ
759f843020 Sponsor permissions 2025-09-16 23:11:19 +09:00
tequ
5ac7bbf51b test SponsorshipRequire flags 2025-09-15 22:48:40 +09:00
tequ
2a27280f1a fix test error 2025-09-15 22:07:43 +09:00
tequ
6c0732ccbe test checks 2025-09-15 20:29:58 +09:00
tequ
20bce64946 test AccountDelete for sponsorship 2025-09-15 19:09:57 +09:00
tequ
110b222579 add MaxFee test 2025-09-15 18:52:42 +09:00
tequ
1cdf7bb745 feePayer 2025-09-15 16:25:16 +09:00
tequ
4abd94e7ac MaxFee 2025-09-15 11:04:47 +09:00
tequ
6aa0331ffe sfAccount to sfOwner 2025-09-13 22:40:19 +09:00
tequ
4eea76ca92 Create Sponsored Account / AccountDelete with ltSponsorship, Sponsored Account 2025-09-13 22:34:54 +09:00
tequ
8e895a3e7d fullly rename 2025-09-13 09:49:30 +09:00
tequ
e589b71ee0 v2. SponsorSet 2025-09-13 09:36:38 +09:00
tequ
02d8f9fbef Sponsor signing 2025-09-08 00:20:14 +09:00
tequ
9ff71aa109 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-09-06 23:11:10 +09:00
tequ
2071b39d6a use helper 2025-08-01 18:33:19 +09:00
tequ
5e65813a52 add reserve checks for SponsorTransfer 2025-08-01 18:21:44 +09:00
tequ
abd8620c97 Add SponsorTransfer 2025-08-01 13:23:41 +09:00
tequ
6e01f233e4 fix fees().accountReserve(0) to fees().reserve 2025-08-01 01:59:08 +09:00
tequ
0bddc95444 clang-format 2025-08-01 01:19:37 +09:00
tequ
67bdd099c8 check reserve 2025-08-01 00:44:11 +09:00
tequ
f0addd81a1 fix addEmptyHolding 2025-07-31 17:43:54 +09:00
tequ
a798c60a08 test Disabled 2025-07-31 00:12:23 +09:00
tequ
198ed72fe3 fix OwnerCount 2025-07-31 00:12:09 +09:00
tequ
d40f8cc232 SponsorReserve,SponsorAccount
- not fully tested
- need to modify: Reserve check where using sfOwnerCount
2025-07-30 23:39:14 +09:00
tequ
5b9c767b07 Merge remote-tracking branch 'upstream/develop' into sponsor 2025-07-29 22:58:03 +09:00
Mayukha Vadari
43c16f35f7 PoC for sponsored reserve 2025-06-17 11:35:23 +09:00
Mayukha Vadari
cad305ac7e PoC for sponsored fees 2025-06-17 11:32:13 +09:00
221 changed files with 14531 additions and 1732 deletions

View File

@@ -153,6 +153,7 @@ Checks: "-*,
readability-use-std-min-max
"
# ---
# bugprone-narrowing-conversions, # this will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs
# readability-inconsistent-declaration-parameter-name, # In this codebase this check will break a lot of arg names
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
# ---

View File

@@ -14,7 +14,6 @@ libxrpl.ledger > xrpl.json
libxrpl.ledger > xrpl.ledger
libxrpl.ledger > xrpl.nodestore
libxrpl.ledger > xrpl.protocol
libxrpl.ledger > xrpl.server
libxrpl.ledger > xrpl.shamap
libxrpl.net > xrpl.basics
libxrpl.net > xrpl.net
@@ -221,7 +220,6 @@ xrpl.core > xrpl.protocol
xrpl.json > xrpl.basics
xrpl.ledger > xrpl.basics
xrpl.ledger > xrpl.protocol
xrpl.ledger > xrpl.server
xrpl.ledger > xrpl.shamap
xrpl.net > xrpl.basics
xrpl.nodestore > xrpl.basics

View File

@@ -20,8 +20,6 @@ _SANITIZER_SUFFIX: dict[str, str] = {
def get_cmake_args(build_type: str, extra_args: str) -> str:
"""Get the full list of CMake arguments for a config."""
args = _BASE_CMAKE_ARGS.copy()
if build_type == "Release":
args.append("-Dassert=ON")
if extra_args:
args.extend(extra_args.split())
return " ".join(args)

View File

@@ -1,5 +1,5 @@
{
"image_tag": "sha-63ffdc3",
"image_tag": "sha-fe4c8ae",
"configs": {
"ubuntu": [
{
@@ -10,7 +10,7 @@
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug"],
"build_type": ["Debug", "Release"],
"arch": ["amd64"],
"sanitizers": ["address", "undefinedbehavior"]
},
@@ -68,7 +68,7 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-63ffdc3"
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
}
],
@@ -77,7 +77,7 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-63ffdc3"
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
}
]
}

View File

@@ -9,12 +9,20 @@ on:
- "flake.nix"
- "flake.lock"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/install-sanitizer-libs.sh"
pull_request:
paths:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/install-sanitizer-libs.sh"
workflow_dispatch:
concurrency:

View File

@@ -41,7 +41,7 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

View File

@@ -121,6 +121,11 @@ jobs:
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}"
- name: Check tools
env:
CHECK_TOOLS_SKIP_CLONE: "1"
run: ./bin/check-tools.sh
- name: Print build environment
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
@@ -164,6 +169,27 @@ jobs:
${CMAKE_ARGS} \
..
# Export the sanitizer options before any instrumented binary runs. The
# protocol code-gen and build steps below invoke instrumented dependency
# tools (protoc, grpc), so setting UBSAN_OPTIONS here lets the UBSan
# suppression list silence their diagnostics too, not just at test time.
# GITHUB_WORKSPACE (not the github.workspace context) is used so the path
# resolves correctly inside the container job.
- name: Set sanitizer options
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
env:
CONFIG_NAME: ${{ inputs.config_name }}
run: |
SUPP="${GITHUB_WORKSPACE}/sanitizers/suppressions"
ASAN_OPTS="include=${SUPP}/runtime-asan-options.txt:suppressions=${SUPP}/asan.supp"
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
fi
echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV}
echo "TSAN_OPTIONS=include=${SUPP}/runtime-tsan-options.txt:suppressions=${SUPP}/tsan.supp" >>${GITHUB_ENV}
echo "UBSAN_OPTIONS=include=${SUPP}/runtime-ubsan-options.txt:suppressions=${SUPP}/ubsan.supp" >>${GITHUB_ENV}
echo "LSAN_OPTIONS=include=${SUPP}/runtime-lsan-options.txt:suppressions=${SUPP}/lsan.supp" >>${GITHUB_ENV}
- name: Check protocol autogen files are up-to-date
working-directory: ${{ env.BUILD_DIR }}
env:
@@ -279,20 +305,6 @@ jobs:
run: |
./xrpld --version | grep libvoidstar
- name: Set sanitizer options
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
env:
CONFIG_NAME: ${{ inputs.config_name }}
run: |
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
fi
echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV}
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >>${GITHUB_ENV}
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >>${GITHUB_ENV}
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >>${GITHUB_ENV}
- name: Run the separate tests
if: ${{ !inputs.build_only }}
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}

View File

@@ -20,9 +20,12 @@ env:
BUILD_DIR: build
BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check
OUTPUT_FILE: clang-tidy-output.txt
DIFF_FILE: clang-tidy-git-diff.txt
ISSUE_FILE: clang-tidy-issue.md
OUTPUT_FILE: /tmp/clang-tidy-output.txt
FILTERED_OUTPUT_FILE: /tmp/clang-tidy-filtered-output.txt
DIFF_FILE: /tmp/clang-tidy-git-diff.txt
ISSUE_FILE: /tmp/clang-tidy-issue.md
COMPILER: clang
jobs:
determine-files:
@@ -36,7 +39,7 @@ jobs:
needs: [determine-files]
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-63ffdc3"
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fe4c8ae"
permissions:
contents: read
issues: write
@@ -59,7 +62,7 @@ jobs:
- name: Set compiler environment
uses: ./.github/actions/set-compiler-env
with:
compiler: clang
compiler: ${{ env.COMPILER }}
- name: Setup Conan
uses: ./.github/actions/setup-conan
@@ -150,21 +153,21 @@ jobs:
run: |
if [ -f "${OUTPUT_FILE}" ]; then
# Extract lines containing 'error:', 'warning:', or 'note:'
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >filtered-output.txt || true
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >"${FILTERED_OUTPUT_FILE}" || true
# If filtered output is empty, use original (might be a different error format)
if [ ! -s filtered-output.txt ]; then
cp "${OUTPUT_FILE}" filtered-output.txt
if [ ! -s "${FILTERED_OUTPUT_FILE}" ]; then
cp "${OUTPUT_FILE}" "${FILTERED_OUTPUT_FILE}"
fi
# Truncate if too large
head -c 60000 filtered-output.txt >>"${ISSUE_FILE}"
if [ "$(wc -c <filtered-output.txt)" -gt 60000 ]; then
head -c 60000 "${FILTERED_OUTPUT_FILE}" >>"${ISSUE_FILE}"
if [ "$(wc -c <"${FILTERED_OUTPUT_FILE}")" -gt 60000 ]; then
echo "" >>"${ISSUE_FILE}"
echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}"
fi
rm filtered-output.txt
rm "${FILTERED_OUTPUT_FILE}"
else
echo "No output file found" >>"${ISSUE_FILE}"
fi

View File

@@ -40,7 +40,7 @@ defaults:
jobs:
upload:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

391
BUILD.md
View File

@@ -1,26 +1,57 @@
| :warning: **WARNING** :warning: |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). |
| :warning: **WARNING** :warning: |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md).<br><br>These instructions also assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, you can read our [crash course](./docs/build/conan.md) or the official [Getting Started][conan-getting-started] walkthrough. |
> These instructions also assume a basic familiarity with Conan and CMake.
> If you are unfamiliar with Conan, you can read our
> [crash course](./docs/build/conan.md) or the official [Getting Started][3]
> walkthrough.
## Minimum Requirements
## Branches
See [System Requirements](https://xrpl.org/system-requirements.html).
For a stable release, choose the `master` branch or one of the [tagged
releases](https://github.com/XRPLF/rippled/releases).
Building xrpld generally requires Git, Python, Conan, CMake, and a C++
compiler.
- [Python](https://www.python.org/downloads/)
- [Conan](https://conan.io/downloads.html)
- [CMake](https://cmake.org/download/)
You can verify that the required tools are installed and runnable with:
```bash
git checkout master
./bin/check-tools.sh
```
For the latest release candidate, choose the `release` branch.
`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are:
```bash
git checkout release
```
| Compiler | Version |
| ----------- | --------------- |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44[^windows] |
## Operating Systems
Please see the [environment setup guide](./docs/build/environment.md) for detailed instructions for all platforms.
### Linux
The Ubuntu Linux distribution has received the highest level of quality
assurance, testing, and support. We also support Red Hat and use Debian
internally.
Our Linux CI tooling is distro-independent and uses a Nix-based environment, so it should be possible to build on other Linux distributions as well, although we have not tested them.
### macOS
Many `xrpld` engineers use macOS for development.
### Windows
Windows is used by some engineers for development only.
[^windows]: Windows is not recommended for production use.
## Steps
### Branches
For the latest set of untested features, or to contribute, choose the `develop`
branch.
@@ -29,55 +60,15 @@ branch.
git checkout develop
```
## Minimum Requirements
For a release candidate, choose the relevant release branch, e.g.
`release/3.2.x`.
See [System Requirements](https://xrpl.org/system-requirements.html).
```bash
git checkout release/3.2.x
```
Building xrpld generally requires git, Python, Conan, CMake, and a C++
compiler. Some guidance on setting up such a [C++ development environment can be
found here](./docs/build/environment.md).
- [Python 3.11](https://www.python.org/downloads/), or higher
- [Conan 2.17](https://conan.io/downloads.html)[^1], or higher
- [CMake 3.22](https://cmake.org/download/), or higher
[^1]:
It is possible to build with Conan 1.60+, but the instructions are
significantly different, which is why we are not recommending it.
`xrpld` is written in the C++23 dialect and includes the `<concepts>` header.
The [tested compiler versions][2] are:
| Compiler | Version |
| ----------- | --------- |
| GCC | 15 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44[^3] |
### Linux
The Ubuntu Linux distribution has received the highest level of quality
assurance, testing, and support. We also support Red Hat and use Debian
internally.
Here are [sample instructions for setting up a C++ development environment on
Linux](./docs/build/environment.md#linux).
### Mac
Many xrpld engineers use macOS for development.
Here are [sample instructions for setting up a C++ development environment on
macOS](./docs/build/environment.md#macos).
### Windows
Windows is used by some engineers for development only.
[^3]: Windows is not recommended for production use.
## Steps
For a stable release, choose one of the [tagged
releases](https://github.com/XRPLF/rippled/releases).
### Set Up Conan
@@ -86,18 +77,11 @@ Conan, CMake, and a C++ compiler, you may need to set up your Conan profile.
These instructions assume a basic familiarity with Conan and CMake. If you are
unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official
[Getting Started][3] walkthrough.
[Getting Started][conan-getting-started] walkthrough.
#### Conan lockfile
#### Profiles
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html),
which has to be updated every time dependencies change.
Please see the [instructions on how to regenerate the lockfile](conan/lockfile/README.md).
#### Default profile
We recommend that you import the provided `conan/profiles/default` profile:
We recommend that you install our Conan profiles:
```bash
conan config install conan/profiles/ -tf $(conan config home)/profiles/
@@ -109,222 +93,15 @@ You can check your Conan profile by running:
conan profile show
```
#### Custom profile
If the default profile is not suitable for your environment, you can create a custom profile and pass it to Conan.
More information on customizing Conan can be found in the [Advanced Conan configuration](./docs/build/advanced_conan.md).
If the default profile does not work for you and you do not yet have a Conan
profile, you can create one by running:
#### Add xrplf remote
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
```bash
conan profile detect
```
You may need to make changes to the profile to suit your environment. You can
refer to the provided `conan/profiles/default` profile for inspiration, and you
may also need to apply the required [tweaks](#conan-profile-tweaks) to this
default profile.
### Patched recipes
Occasionally, we need patched recipes or recipes not present in Conan Center.
We maintain a fork of the Conan Center Index
[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes.
To ensure our patched recipes are used, you must add our Conan remote at a
higher index than the default Conan Center remote, so it is consulted first. You
can do this by running:
```bash
conan remote add --index 0 xrplf https://conan.ripplex.io
```
Alternatively, you can pull our recipes from the repository and export them locally:
```bash
# Define which recipes to export.
recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
# Selectively check out the recipes from our CCI fork.
cd external
mkdir -p conan-center-index
cd conan-center-index
git init
git remote add origin git@github.com:XRPLF/conan-center-index.git
git sparse-checkout init
for recipe in "${recipes[@]}"; do
echo "Checking out recipe '${recipe}'..."
git sparse-checkout add recipes/${recipe}
done
git fetch origin master
git checkout master
./export_all.sh
cd ../../
```
In the case we switch to a newer version of a dependency that still requires a
patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the
updated dependencies with the newer version. However, if we switch to a newer
version that no longer requires a patch, no action is required on your part, as
the new recipe will be automatically pulled from the official Conan Center.
> [!NOTE]
> You might need to add `--lockfile=""` to your `conan install` command
> to avoid automatic use of the existing `conan.lock` file when you run
> `conan export` manually on your machine
>
> This is not recommended though, as you might end up using different revisions of recipes.
### Conan profile tweaks
#### Missing compiler version
If you see an error similar to the following after running `conan profile show`:
```text
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
'15.0', '16', '16.0']
Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting"
```
you need to add your compiler to the list of compiler versions in
`$(conan config home)/settings_user.yml`, by adding the required version number(s)
to the `version` array specific for your compiler. For example:
```yaml
compiler:
apple-clang:
version: ["17.0"]
```
#### Multiple compilers
If you have multiple compilers installed, make sure to select the one to use in
your default Conan configuration **before** running `conan profile detect`, by
setting the `CC` and `CXX` environment variables.
For example, if you are running MacOS and have [homebrew
LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a
compiler in the new Conan profile:
```bash
export CC=$(brew --prefix llvm@18)/bin/clang
export CXX=$(brew --prefix llvm@18)/bin/clang++
conan profile detect
```
You should also explicitly set the path to the compiler in the profile file,
which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the
selected Conan profile. For example:
```text
[conf]
tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'}
```
#### Multiple profiles
You can manage multiple Conan profiles in the directory
`$(conan config home)/profiles`, for example renaming `default` to a different
name and then creating a new `default` profile for a different compiler.
#### Select language
The default profile created by Conan will typically select different C++ dialect
than C++23 used by this project. You should set `23` in the profile line
starting with `compiler.cppstd=`. For example:
```bash
sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default
```
#### Select standard library in Linux
**Linux** developers will commonly have a default Conan [profile][] that
compiles with GCC and links with libstdc++. If you are linking with libstdc++
(see profile setting `compiler.libcxx`), then you will need to choose the
`libstdc++11` ABI:
```bash
sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default
```
#### Select architecture and runtime in Windows
**Windows** developers may need to use the x64 native build tools. An easy way
to do that is to run the shortcut "x64 Native Tools Command Prompt" for the
version of Visual Studio that you have installed.
Windows developers must also build `xrpld` and its dependencies for the x64
architecture:
```bash
sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default
```
**Windows** developers also must select static runtime:
```bash
sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default
```
#### Clang workaround for grpc
If your compiler is clang, version 19 or later, or apple-clang, version 17 or
later, you may encounter a compilation error while building the `grpc`
dependency:
```text
In file included from .../lib/promise/try_seq.h:26:
.../lib/promise/detail/basic_seq.h:499:38: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
499 | Traits::template CallSeqFactory(f_, *cur_, std::move(arg)));
| ^
```
The workaround for this error is to add two lines to profile:
```text
[conf]
tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw']
```
#### Workaround for gcc 12
If your compiler is gcc, version 12, and you have enabled `werr` option, you may
encounter a compilation error such as:
```text
/usr/include/c++/12/bits/char_traits.h:435:56: error: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 9223372036854775810 or more bytes at offsets [2, 9223372036854775807] and 1 may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict]
435 | return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
| ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors
```
The workaround for this error is to add two lines to your profile:
```text
[conf]
tools.build:cxxflags=['-Wno-restrict']
```
#### Workaround for clang 16
If your compiler is clang, version 16, you may encounter compilation error such
as:
```text
In file included from .../boost/beast/websocket/stream.hpp:2857:
.../boost/beast/websocket/impl/read.hpp:695:17: error: call to 'async_teardown' is ambiguous
async_teardown(impl.role, impl.stream(),
^~~~~~~~~~~~~~
```
The workaround for this error is to add two lines to your profile:
```text
[conf]
tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS']
conan remote add --index 0 --force xrplf https://conan.ripplex.io
```
### Set Up Ccache
@@ -333,14 +110,7 @@ To speed up repeated compilations, we recommend that you install
[ccache](https://ccache.dev), a tool that wraps your compiler so that it can
cache build objects locally.
#### Linux
You can install it using the package manager, e.g. `sudo apt install ccache`
(Ubuntu) or `sudo dnf install ccache` (RHEL).
#### macOS
You can install it using Homebrew, i.e. `brew install ccache`.
On Linux and macOS, `ccache` is included in the [Nix development shell](./docs/build/nix.md).
#### Windows
@@ -549,7 +319,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| Option | Default Value | Description |
| ---------- | ------------- | -------------------------------------------------------------- |
| `assert` | OFF | Enable assertions. |
| `assert` | OFF | Force enabling assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
@@ -557,7 +327,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| `werr` | OFF | Treat compilation warnings as errors |
| `wextra` | OFF | Enable additional compilation warnings |
[Unity builds][5] may be faster for the first build (at the cost of much more
[Unity builds][unity-build] may be faster for the first build (at the cost of much more
memory) since they concatenate sources into fewer translation units. Non-unity
builds may be faster for incremental builds, and can be helpful for detecting
`#include` omissions.
@@ -583,14 +353,14 @@ After any updates or changes to dependencies, you may need to do the following:
conan remove '*'
```
3. Re-run [conan export](#patched-recipes) if needed.
4. [Regenerate lockfile](#conan-lockfile).
3. Re-run [conan export](./docs/build/advanced_conan.md#patched-recipes) if needed.
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
5. Re-run [conan install](#build-and-test).
#### ERROR: Package not resolved
If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`,
please add `xrplf` remote or re-run `conan export` for [patched recipes](#patched-recipes).
please [add `xrplf` remote](#add-xrplf-remote) or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
### `protobuf/port_def.inc` file not found
@@ -610,28 +380,9 @@ For example, if you want to build Debug:
1. For conan install, pass `--settings build_type=Debug`
2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug`
## Add a Dependency
If you want to experiment with a new package, follow these steps:
1. Search for the package on [Conan Center](https://conan.io/center/).
2. Modify [`conanfile.py`](./conanfile.py):
- Add a version of the package to the `requires` property.
- Change any default options for the package by adding them to the
`default_options` property (with syntax `'$package:$option': $value`).
3. Modify [`CMakeLists.txt`](./CMakeLists.txt):
- Add a call to `find_package($package REQUIRED)`.
- Link a library from the package to the target `xrpl_libs`
(search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`).
4. Start coding! Don't forget to include whatever headers you need from the package.
[1]: https://github.com/conan-io/conan-center-index/issues/13168
[2]: https://en.cppreference.com/w/cpp/compiler_support/20
[3]: https://docs.conan.io/en/latest/getting_started.html
[5]: https://en.wikipedia.org/wiki/Unity_build
[6]: https://github.com/boostorg/beast/issues/2648
[7]: https://github.com/boostorg/beast/issues/2661
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
[conan-getting-started]: https://docs.conan.io/en/latest/getting_started.html
[unity-build]: https://en.wikipedia.org/wiki/Unity_build
[gcovr]: https://gcovr.com/en/stable/getting-started.html
[python-pip]: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/
[build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
[profile]: https://docs.conan.io/en/latest/reference/profiles.html

View File

@@ -14,9 +14,9 @@ The following branches exist in the main project repository:
- `develop`: The latest set of unreleased features, and the most common
starting point for contributions.
- `release`: The latest beta release or release candidate.
- `master`: The latest stable release.
- `gh-pages`: The documentation for this project, built by Doxygen.
- `release/*` (e.g. `release/3.2.x`): Release branches, one per release line,
holding the latest release candidate, or stable release for that line.
Stable releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases).
The tip of each branch must be signed. In order for GitHub to sign a
squashed commit that it builds from your pull request, GitHub must know
@@ -130,11 +130,9 @@ tl;dr
## Pull requests
In general, pull requests use `develop` as the base branch.
The exceptions are
- Fixes and improvements to a release candidate use `release` as the
base.
- Hotfixes use `master` as the base.
The exceptions are fixes, improvements, and hotfixes for an existing release,
which use that release's branch (e.g. `release/3.2.x`) as the base.
If your changes are not quite ready, but you want to make it easily available
for preliminary examination or review, you can create a "Draft" pull request.
@@ -216,7 +214,7 @@ coherent rather than a set of _thou shalt not_ commandments.
## Formatting
All code must conform to `clang-format` version 21,
All code must conform to `clang-format` version 22,
according to the settings in [`.clang-format`](./.clang-format),
unless the result would be unreasonably difficult to read or maintain.
To demarcate lines that should be left as-is, surround them with comments like
@@ -261,7 +259,7 @@ This ensures that configuration changes don't introduce new warnings across the
### Installing clang-tidy
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for platform-specific installation instructions.
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for how to get clang-tidy.
### Running clang-tidy locally

158
bin/check-tools.sh Executable file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env bash
#
# check-tools.sh — verify the xrpld development tooling is present and runnable.
#
# Works on Linux, macOS, and Windows (Git Bash / MSYS). For every expected tool
# it runs a version probe, collecting anything that is missing or fails to run,
# and prints a summary at the end (exiting non-zero if anything is missing).
#
# The tool set is platform-aware:
# - Linux: the full Nix CI environment (see nix/packages.nix, nix/ci-env.nix),
# with GCC, Clang and the sanitizer/coverage tooling. This script is
# run during the Nix Docker image build (nix/docker/Dockerfile), so
# the Linux list is kept in sync with that environment.
# - macOS: the same tooling, minus GCC/g++/gcov/mold
# - Windows: the core build tools only (CMake, Conan, Git, Python).
# MSVC is expected to be provided separately and is not checked here.
#
# Some tools (clang-format, doxygen, gcovr, gh, git-cliff, gpg, pre-commit,
# run-clang-tidy) are present in our Linux CI images and in local development
# setups, but not in the macOS CI environment. They are checked everywhere
# except when running in CI on macOS.
#
# Environment variables:
# CI if set, skip the tools above when on macOS.
# CHECK_TOOLS_SKIP_CLONE if set, skip the git-over-HTTPS connectivity check.
set -uo pipefail
missing=()
checked=0
# check <name> [probe-command...]
# Runs the probe (default: "<name> --version") quietly. Records <name> as
# missing if the command is not found or exits non-zero.
check() {
local name="$1"
shift
local -a probe=("$@")
if [ "${#probe[@]}" -eq 0 ]; then
probe=("${name}" --version)
fi
echo "Checking ${name}..."
checked=$((checked + 1))
if "${probe[@]}" | head -n 1; then
printf ' [ ok ] %s\n' "${name}"
else
printf ' [MISS] %s\n' "${name}"
missing+=("${name}")
fi
}
case "$(uname -s)" in
Linux*) os=linux ;;
Darwin*) os=macos ;;
MINGW* | MSYS* | CYGWIN*) os=windows ;;
*)
echo "Unknown OS: $(uname -s)" >&2
exit 1
;;
esac
echo "Detected OS: ${os} ($(uname -s) $(uname -m))"
echo
echo "Core build tools:"
check cmake
check conan
check git
if [ "${os}" = "windows" ]; then
check python python --version
else
check python3
fi
# The full development toolchain. Available from Nix on Linux and macOS; on
# Windows these are typically not installed, so they are skipped.
if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
echo
echo "Development tooling:"
check ccache
check clang
check clang++
check ClangBuildAnalyzer
check curl
check file
check less
check make
check netstat which netstat
check ninja
check perl
check pkg-config
check vim
# These tools are present in our Linux CI images and in local development
# setups, but not in the macOS CI environment. So check them everywhere
# except when running in CI on macOS.
if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then
check clang-format
check doxygen
check gcovr
check gh
check git-cliff
check gpg
# pre-commit, or its alternative implementation prek
check pre-commit sh -c 'pre-commit --version || prek --version'
check run-clang-tidy run-clang-tidy --help
fi
fi
# GCC is the default compiler on Linux. macOS uses the system Apple Clang
# instead, so GCC/g++/gcov are not expected there.
if [ "${os}" = "linux" ]; then
echo
echo "GCC toolchain:"
check gcc
check g++
check gcov
echo
echo "Mold:"
check mold
fi
if [ "${os}" = "windows" ]; then
echo
echo "Note: on Windows the C++ compiler is MSVC, which is provided"
echo " separately (e.g. via Visual Studio) and is not checked here."
fi
# A simple test to verify that git can clone a repository over HTTPS
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
if [ -n "${CHECK_TOOLS_SKIP_CLONE:-}" ]; then
echo
echo "Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set)."
else
echo
echo "Connectivity check:"
checked=$((checked + 1))
tmp_clone="$(mktemp -d)"
if git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions" >/dev/null 2>&1; then
printf ' [ ok ] git clone over HTTPS\n'
else
printf ' [MISS] git clone over HTTPS\n'
missing+=("git-https-clone")
fi
rm -rf "${tmp_clone}"
fi
echo
if [ "${#missing[@]}" -eq 0 ]; then
echo "All ${checked} checked tools are present and runnable."
else
echo "Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
for tool in "${missing[@]}"; do
echo " - ${tool}" >&2
done
exit 1
fi

View File

@@ -109,6 +109,7 @@ words:
- enabled
- enablerepo
- endmacro
- envrc
- exceptioned
- EXPECT_STREQ
- Falco
@@ -233,8 +234,10 @@ words:
- pyenv
- pyparsing
- qalloc
- qbsprofile
- queuable
- Raphson
- rcflags
- replayer
- rerere
- retriable
@@ -268,6 +271,8 @@ words:
- sles
- soci
- socidb
- sponsee
- sponsees
- SRPMS
- sslws
- statsd

193
docs/build/advanced_conan.md vendored Normal file
View File

@@ -0,0 +1,193 @@
# Advanced Conan configuration
This document provides advanced instructions for setting up and configuring Conan for `xrpld` development: custom profiles, the lockfile, patched recipes, and profile tweaks.
## Custom profile
If the default profile does not work for you and you do not yet have a Conan
profile, you can create one by running:
```bash
conan profile detect
```
You may need to make changes to the profile to suit your environment. You can
refer to the provided `conan/profiles/default` profile for inspiration, and you
may also need to apply the required [tweaks](#conan-profile-tweaks) to this
default profile.
## Conan lockfile
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html),
which has to be updated every time dependencies change.
Please see the [instructions on how to regenerate the lockfile](../../conan/lockfile/README.md).
## Patched recipes
Occasionally, we need patched recipes or recipes not present in Conan Center.
We maintain a fork of the Conan Center Index
[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes.
To ensure our patched recipes are used, you must add our Conan remote at a
higher index than the default Conan Center remote, so it is consulted first. You
can do this by running:
```bash
conan remote add --index 0 --force xrplf https://conan.ripplex.io
```
Alternatively, you can pull our recipes from the repository and export them locally:
```bash
# Define which recipes to export.
recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
# Selectively check out the recipes from our CCI fork.
cd external
mkdir -p conan-center-index
cd conan-center-index
git init
git remote add origin git@github.com:XRPLF/conan-center-index.git
git sparse-checkout init
for recipe in "${recipes[@]}"; do
echo "Checking out recipe '${recipe}'..."
git sparse-checkout add recipes/${recipe}
done
git fetch origin master
git checkout master
./export_all.sh
cd ../../
```
In the case we switch to a newer version of a dependency that still requires a
patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the
updated dependencies with the newer version. However, if we switch to a newer
version that no longer requires a patch, no action is required on your part, as
the new recipe will be automatically pulled from the official Conan Center.
> [!NOTE]
> You might need to add `--lockfile=""` to your `conan install` command
> to avoid automatic use of the existing `conan.lock` file when you run
> `conan export` manually on your machine
>
> This is not recommended though, as you might end up using different revisions of recipes.
## Conan profile tweaks
### Missing compiler version
If you see an error similar to the following after running `conan profile show`:
```text
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
'15.0', '16', '16.0']
Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting"
```
you need to create `$(conan config home)/settings_user.yml` file if it doesn't exist and add the required version number(s)
to the `version` array specific for your compiler. For example:
```yaml
compiler:
apple-clang:
version: ["17.0"]
```
### Multiple compilers
If you have multiple compilers installed, make sure to select the one to use in
your default Conan configuration **before** running `conan profile detect`, by
setting the `CC` and `CXX` environment variables.
For example, if you are running MacOS and have [homebrew
LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a
compiler in the new Conan profile:
```bash
export CC=$(brew --prefix llvm@18)/bin/clang
export CXX=$(brew --prefix llvm@18)/bin/clang++
conan profile detect
```
You should also explicitly set the path to the compiler in the profile file,
which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the
selected Conan profile. For example:
```text
[conf]
tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'}
```
### Multiple profiles
You can manage multiple Conan profiles in the directory
`$(conan config home)/profiles`, for example renaming `default` to a different
name and then creating a new `default` profile for a different compiler.
### Select language
The default profile created by Conan will typically select different C++ dialect
than C++23 used by this project. You should set `23` in the profile line
starting with `compiler.cppstd=`. For example:
```bash
sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default
```
### Select standard library in Linux
**Linux** developers will commonly have a default Conan [profile][] that
compiles with GCC and links with libstdc++. If you are linking with libstdc++
(see profile setting `compiler.libcxx`), then you will need to choose the
`libstdc++11` ABI:
```bash
sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default
```
### Select architecture and runtime in Windows
**Windows** developers may need to use the x64 native build tools. An easy way
to do that is to run the shortcut "x64 Native Tools Command Prompt" for the
version of Visual Studio that you have installed.
Windows developers must also build `xrpld` and its dependencies for the x64
architecture:
```bash
sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default
```
**Windows** developers also must select static runtime:
```bash
sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default
```
## Add a Dependency
If you want to experiment with a new package, follow these steps:
1. Search for the package on [Conan Center](https://conan.io/center/).
2. Modify [`conanfile.py`](../../conanfile.py):
- Add a version of the package to the `requires` property.
- Change any default options for the package by adding them to the
`default_options` property (with syntax `'$package:$option': $value`).
3. Regenerate the [Conan lockfile](../../conan/lockfile/README.md) so the new
dependency is captured:
```bash
./conan/lockfile/regenerate.sh
```
4. Modify [`CMakeLists.txt`](../../CMakeLists.txt):
- Add a call to `find_package($package REQUIRED)`.
- Link a library from the package to the target `xrpl_libs`
(search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`).
5. Start coding! Don't forget to include whatever headers you need from the package.
[profile]: https://docs.conan.io/2/reference/config_files/profiles.html

2
docs/build/conan.md vendored
View File

@@ -115,7 +115,7 @@ By default, Conan will use the profile named "default".
[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html
[pcf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-configuration-file
[prefix_path]: https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html
[profile]: https://docs.conan.io/en/latest/reference/profiles.html
[profile]: https://docs.conan.io/2/reference/config_files/profiles.html
[pvf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-version-file
[runtime]: https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html
[search]: https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure

View File

@@ -1,69 +1,73 @@
Our [build instructions][BUILD.md] assume you have a C++ development
environment complete with Git, Python, Conan, CMake, and a C++ compiler.
This document exists to help readers set one up on any of the Big Three
platforms: Linux, macOS, or Windows.
As an alternative to system packages, the Nix development shell can be used to provide a development environment. See [using nix development shell](./nix.md) for more details.
This document explains how to set one up.
[BUILD.md]: ../../BUILD.md
## Linux
## Tested compiler versions
Package ecosystems vary across Linux distributions,
so there is no one set of instructions that will work for every Linux user.
The instructions below are written for Debian 12 (Bookworm).
`xrpld` is built in the **C++23** dialect by default.
Make sure your toolchain is recent enough — the compiler versions currently tested in CI are:
```
export GCC_RELEASE=12
sudo apt update
sudo apt install --yes gcc-${GCC_RELEASE} g++-${GCC_RELEASE} python3-pip \
python-is-python3 python3-venv python3-dev curl wget ca-certificates \
git build-essential cmake ninja-build libc6-dev
sudo pip install --break-system-packages conan
| Compiler | Version |
| ----------- | ------- |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44 |
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-${GCC_RELEASE} 999
sudo update-alternatives --install \
/usr/bin/gcc gcc /usr/bin/gcc-${GCC_RELEASE} 100 \
--slave /usr/bin/g++ g++ /usr/bin/g++-${GCC_RELEASE} \
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-${GCC_RELEASE} \
--slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-${GCC_RELEASE} \
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-${GCC_RELEASE} \
--slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_RELEASE} \
--slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-${GCC_RELEASE} \
--slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-${GCC_RELEASE} \
--slave /usr/bin/lto-dump lto-dump /usr/bin/lto-dump-${GCC_RELEASE}
sudo update-alternatives --auto cc
sudo update-alternatives --auto gcc
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
Older compilers may fail to build the latest `develop` code: the codebase now
relies on C++23 features and has been adjusted for `clang-tidy`.
If the latest code doesn't build for you, update your build toolchain first.
## Linux and macOS
The **recommended way** to get a development environment on Linux and macOS is
the Nix development shell. It provides the exact tooling used in CI — `git`,
`python`, `conan`, `cmake`, `clang-tidy`, `clang-format`, and everything else —
with a single command and without installing anything system-wide:
```bash
nix --experimental-features 'nix-command flakes' develop
```
If you use different Linux distribution, hope the instruction above can guide
you in the right direction. We try to maintain compatibility with all recent
compiler releases, so if you use a rolling distribution like e.g. Arch or CentOS
then there is a chance that everything will "just work".
On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses
your **system-wide Apple Clang** as the compiler, so you still need to manage
its version (see below).
## macOS
See [Using the Nix development shell](./nix.md) for installation and usage
details, including how to select a different compiler.
Open a Terminal and enter the below command to bring up a dialog to install
the command line developer tools.
Once it is finished, this command should return a version greater than the
minimum required (see [BUILD.md][]).
> [!NOTE]
> Using Nix is not mandatory. Any custom environment (Homebrew packages or
> anything else) will continue to work, but then it is up to you to keep it in
> sync with the environment used in CI. Nix unifies the development environment
> for everyone and synchronizes updates, which is why we recommend it.
```
### macOS: managing the Apple Clang version
Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler
version is whatever your installed Xcode (or Command Line Tools) provides. The
following command should return a version greater than or equal to the
[minimum required](#tested-compiler-versions):
```bash
clang --version
```
### Install Xcode Specific Version (Optional)
If you develop other applications using XCode you might be consistently updating to the newest version of Apple Clang.
This will likely cause issues building xrpld. You may want to install a specific version of Xcode:
If you develop other applications using Xcode, you might be consistently
updating to the newest version of Apple Clang, which will likely cause issues
building xrpld. You may want to install and pin a specific version of Xcode:
1. **Download Xcode**
- Visit [Apple Developer Downloads](https://developer.apple.com/download/more/)
- Sign in with your Apple Developer account
- Search for an Xcode version that includes **Apple Clang (Expected Version)**
- Search for an Xcode version that includes the expected Apple Clang version
- Download the `.xip` file
2. **Install and Configure Xcode**
2. **Install and configure Xcode**
```bash
# Extract the .xip file and rename for version management
@@ -79,62 +83,28 @@ This will likely cause issues building xrpld. You may want to install a specific
export DEVELOPER_DIR=/Applications/Xcode_16.2.app/Contents/Developer
```
The command line developer tools should include Git too:
## Windows
```
git --version
```
Nix is not available on Windows, so the required tools have to be installed
manually:
Install [Homebrew][],
use it to install [pyenv][],
use it to install Python,
and use it to install Conan:
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with the
**"Desktop development with C++"** workload — this provides MSVC and the
"x64 Native Tools Command Prompt".
- [Git for Windows](https://git-scm.com/download/win)
- [Python 3.11](https://www.python.org/downloads/), or higher
- [Conan 2.17](https://conan.io/downloads.html), or higher
- [CMake 3.22](https://cmake.org/download/), or higher
[Homebrew]: https://brew.sh/
[pyenv]: https://github.com/pyenv/pyenv
```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew update
brew install xz
brew install pyenv
pyenv install 3.11
pyenv global 3.11
eval "$(pyenv init -)"
pip install 'conan'
```
Install CMake with Homebrew too:
```
brew install cmake
```
> [!NOTE]
> Windows is used for development only and is not recommended for production.
## Clang-tidy
Clang-tidy is required to run static analysis checks locally (see [CONTRIBUTING.md](../../CONTRIBUTING.md)).
It is not required to build the project. Currently this project uses clang-tidy version 21.
`clang-tidy` is required to run static analysis checks locally (see
[CONTRIBUTING.md](../../CONTRIBUTING.md)). It is not required to build the
project. This project currently uses `clang-tidy` version 22.
### Linux
LLVM 21 is not available in the default Debian 12 (Bookworm) repositories.
Install it using the official LLVM apt installer:
```
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21
sudo apt install --yes clang-tidy-21
```
Then use `run-clang-tidy-21` when running clang-tidy locally.
### macOS
Install LLVM 21 via Homebrew:
```
brew install llvm@21
```
Then use `run-clang-tidy` from the LLVM 21 Homebrew prefix when running clang-tidy locally.
On Linux and macOS, the [Nix development shell](./nix.md) provides `clang-tidy`
22 out of the box — run it via `run-clang-tidy`. No separate installation is
needed.

45
docs/build/nix.md vendored
View File

@@ -2,9 +2,12 @@
This guide explains how to use Nix to set up a reproducible development environment for xrpld. Using Nix eliminates the need to manually install utilities and ensures consistent tooling across different machines.
**The Nix development shell is the recommended way to develop xrpld.** It unifies the development environment for everyone and synchronizes updates: the same tooling and compiler versions are used both here and in CI. Any custom environment (Homebrew packages or anything else) will continue to work, but then it is up to you to keep it in sync with the environment used in CI.
## Benefits of Using Nix
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment
- **No system pollution**: Dependencies are isolated and don't affect your system packages
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
- **Quick setup**: Get started with a single command
@@ -28,11 +31,22 @@ This will:
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
- Configure the appropriate compiler for your platform:
- **macOS**: Apple Clang (default system compiler)
- **Linux**: GCC 15
- **Linux**: GCC 15.2 (provided by Nix)
- **macOS**: Apple Clang (your system compiler)
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
### Platform notes
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to
develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats.
- **macOS**: `nix develop` gives you a full environment too. The compiler is
your system-wide Apple Clang, while every other tool — including Conan — is
provided by Nix. Conan has no binary in the Nix cache for macOS, so it is
built from source the first time you enter the shell, which makes the initial
setup slower (this is handled automatically; see
[`nix/devshell.nix`](../../nix/devshell.nix)).
> [!TIP]
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
>
@@ -51,7 +65,7 @@ The first time you run this command, it will take a few minutes to download and
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
Use `nix flake show` to see all the available development shells.
Use `nix develop .#no_compiler` to use the compiler from your system.
Use `nix develop .#no-compiler` to use the compiler from your system.
### Example Usage
@@ -68,12 +82,28 @@ nix develop
### Using a different shell
`nix develop` opens bash by default. If you want to use another shell this could be done by adding `-c` flag. For example:
`nix develop` opens bash by default. To use another shell, pass it with the `-c` flag — this works with any shell, e.g. `zsh` or `fish`:
```bash
# Use zsh
nix develop -c zsh
# Use fish
nix develop -c fish
# Use your login shell
nix develop -c "$SHELL"
```
> [!WARNING]
> Your shell's interactive startup files (e.g. `config.fish`, `.zshrc`) may prepend other directories — most commonly Homebrew — to `$PATH`, which can shadow the tools provided by the Nix shell. After entering, verify that tools resolve into the Nix store:
>
> ```bash
> command -v cmake # should print a /nix/store/... path
> ```
>
> If it doesn't, either adjust your shell configuration so it doesn't override `$PATH`, or use [direnv](#automatic-activation-with-direnv) (below), which loads the environment _after_ your shell config and so takes precedence regardless of the shell you use.
## Building xrpld with Nix
Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.).
@@ -82,6 +112,8 @@ Once inside the Nix development shell, follow the standard [build instructions](
[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory.
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`.
## Conan and Prebuilt Packages
Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source:
@@ -93,3 +125,8 @@ conan install .. --output-folder . --build '*' --settings build_type=Release
## Updating `flake.lock` file
To update `flake.lock` to the latest revision use `nix flake update` command.
## Troubleshooting
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues,
such as `nix develop` failing inside Git worktrees.

61
docs/build/nix_troubleshooting.md vendored Normal file
View File

@@ -0,0 +1,61 @@
# Troubleshooting Nix problems
Common issues encountered when using the [Nix development shell](./nix.md), and
how to resolve them.
## Git worktrees
If `nix develop` fails with an error like:
```
error:
… while fetching the input 'git+file:///path/to/rippled'
error: opening Git repository "/path/to/rippled": unsupported extension name extensions.relativeworktrees (libgit2 error code = 6)
```
then your Nix is linked against a libgit2 older than **1.9.4**. Git 2.48+ writes
the `extensions.relativeWorktrees` config entry when a worktree is created with
relative paths (`git worktree add --relative-paths`, or with
`worktree.useRelativePaths=true`), and older libgit2 versions refuse to open a
repository that uses it. Nix uses libgit2 to read the flake, so evaluation
fails.
> [!IMPORTANT]
> This entry is written to the **shared** repository config, so once any
> relative worktree exists, `nix develop` fails in the main checkout too — not
> just inside the worktree.
### Workarounds
These work today, with any Nix version:
- bypass libgit2 with a `path:` flakeref: `nix develop "path:$PWD"`
(note: this copies the working tree to the store and ignores `.gitignore`); or
- create worktrees with absolute paths (omit `--relative-paths`); or
- clear the extension if you don't need relative worktrees:
`git config --unset extensions.relativeWorktrees`.
### Permanent fix
The fix is in [libgit2 1.9.4](https://github.com/libgit2/libgit2/releases/tag/v1.9.4),
so the real solution is a Nix that links against libgit2 `1.9.4` or newer. Check
which version yours links against:
```bash
nix-store -qR "$(readlink -f "$(command -v nix)")" | grep libgit2
```
> [!WARNING]
> `nix upgrade-nix` does **not** help yet. It installs the build from the
> official [`nix-fallback-paths`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/tools/nix-fallback-paths.nix),
> which is still linked against libgit2 `1.9.2` — there is no new upstream Nix
> release with the fix. (On some systems that build is even the exact store path
> you already have, making the upgrade a no-op.)
nixpkgs has already rebuilt Nix against the fixed libgit2 (e.g. `nix-2.34.7+1`),
so the cleanest path is to reinstall Nix using your usual installation method
once it picks up that rebuild, then re-run the `grep libgit2` check above to
confirm it reports `1.9.4` or newer.
Until then, prefer the workarounds above.

13
flake.lock generated
View File

@@ -2,17 +2,18 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1780749050,
"narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
"lastModified": 1781173989,
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-custom-glibc": {

View File

@@ -1,7 +1,7 @@
{
description = "Nix related things for xrpld";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# nixpkgs snapshot (2020-06-30) that shipped glibc 2.31 as the primary
# version — matches the system libc on Ubuntu 20.04 LTS. Imported
# manually (flake = false) because this revision predates nixpkgs'

View File

@@ -1,29 +0,0 @@
#pragma once
#if XRPL_ROCKSDB_AVAILABLE
// #include <rocksdb2/port/port_posix.h>
#include <rocksdb/cache.h>
#include <rocksdb/compaction_filter.h>
#include <rocksdb/comparator.h>
#include <rocksdb/convenience.h>
#include <rocksdb/db.h>
#include <rocksdb/env.h>
#include <rocksdb/filter_policy.h>
#include <rocksdb/flush_block_policy.h>
#include <rocksdb/iterator.h>
#include <rocksdb/memtablerep.h>
#include <rocksdb/merge_operator.h>
#include <rocksdb/options.h>
#include <rocksdb/perf_context.h>
#include <rocksdb/slice.h>
#include <rocksdb/slice_transform.h>
#include <rocksdb/statistics.h>
#include <rocksdb/status.h>
#include <rocksdb/table.h>
#include <rocksdb/table_properties.h>
#include <rocksdb/transaction_log.h>
#include <rocksdb/types.h>
#include <rocksdb/universal_compaction.h>
#include <rocksdb/write_batch.h>
#endif

View File

@@ -4,7 +4,7 @@
/*
ASAN flags some false positives with sudden jumps in control flow, like
exceptions, or when encountering coroutine stack switches. This macro can be used to disable ASAN
intrumentation for specific functions.
instrumentation for specific functions.
*/
#if defined(__GNUC__) || defined(__clang__)
#define XRPL_NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address", "hwaddress")))

View File

@@ -1,49 +0,0 @@
#pragma once
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/server/InfoSub.h>
#include <memory>
#include <mutex>
namespace xrpl {
/** Listen to public/subscribe messages from a book. */
class BookListeners
{
public:
using pointer = std::shared_ptr<BookListeners>;
BookListeners() = default;
/** Add a new subscription for this book
*/
void
addSubscriber(InfoSub::ref sub);
/** Stop publishing to a subscriber
*/
void
removeSubscriber(std::uint64_t sub);
/** Publish a transaction to subscribers
Publish a transaction to clients subscribed to changes on this book.
Uses havePublished to prevent sending duplicate transactions to clients
that have subscribed to multiple books.
@param jvObj JSON transaction data to publish
@param havePublished InfoSub sequence numbers that have already
published this transaction.
*/
void
publish(MultiApiJson const& jvObj, hash_set<std::uint64_t>& havePublished);
private:
std::recursive_mutex lock_;
hash_map<std::uint64_t, InfoSub::wptr> listeners_;
};
} // namespace xrpl

View File

@@ -1,11 +1,11 @@
#pragma once
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/AcceptedLedgerTx.h>
#include <xrpl/ledger/BookListeners.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/protocol/UintTypes.h>
#include <memory>
@@ -77,34 +77,24 @@ public:
*/
virtual bool
isBookToXRP(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/**
* Process a transaction for order book tracking.
* @param ledger The ledger the transaction was applied to
* @param alTx The transaction to process
* @param jvObj The JSON object of the transaction
*/
virtual void
processTxn(
std::shared_ptr<ReadView const> const& ledger,
AcceptedLedgerTx const& alTx,
MultiApiJson const& jvObj) = 0;
/**
* Get the book listeners for a book.
* @param book The book to get the listeners for
* @return The book listeners for the book
*/
virtual BookListeners::pointer
getBookListeners(Book const&) = 0;
/**
* Create a new book listeners for a book.
* @param book The book to create the listeners for
* @return The new book listeners for the book
*/
virtual BookListeners::pointer
makeBookListeners(Book const&) = 0;
};
/** Extract the set of books affected by a transaction.
*
* Walks the transaction's metadata nodes and collects every order book
* whose offers were created, modified, or deleted. Used by NetworkOPs to
* fan transaction notifications out to book subscribers.
*
* @param alTx The accepted ledger transaction to inspect.
* @param j Journal used to log per-node parsing failures. Inspecting an
* offer node can throw if a required field is missing; in that
* case the bad node is skipped and a warn-level message is
* emitted via @p j. Other affected books in the same transaction
* are still returned.
* @return The set of books whose offers were created, modified, or
* deleted. May be empty for non-offer transactions.
*/
hash_set<Book>
affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j);
} // namespace xrpl

View File

@@ -33,9 +33,96 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer);
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
/** Returns the account reserve, in drops.
Actual owner count can be adjusted by delta in ownerCountAdj
The reserve is calculated as
(ownerCount + "sponsoring object count" - "sponsored object count" + additionalOwnerCount) *
increment + (1 if not sponsored account + sponsoringAccountCount) * "reserve base"
*/
[[nodiscard]] XRPAmount
accountReserve(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj = 0,
std::int32_t reserveCountAdj = 0);
[[nodiscard]] inline XRPAmount
accountReserve(
ReadView const& view,
AccountID const& id,
beast::Journal j,
std::int32_t ownerCountAdj = 0,
std::int32_t reserveCountAdj = 0)
{
return accountReserve(view, view.read(keylet::account(id)), j, ownerCountAdj, reserveCountAdj);
}
XRPAmount
baseAccountReserve(ReadView const& view, std::int32_t ownerCount);
[[nodiscard]] TER
checkInsufficientReserve(
ReadView const& view,
STTx const& tx,
SLE::const_ref accSle,
STAmount const& accBalance,
SLE::const_ref sponsorSle,
std::int32_t ownerCountDelta,
std::int32_t reserveCountDelta = 0,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
std::uint32_t
ownerCount(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj = 0);
/** Adjust the owner count up or down. */
void
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j);
adjustOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
inline void
adjustOwnerCount(
ApplyView& view,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
adjustOwnerCount(
view,
view.peek(keylet::account(account)),
sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(),
amount,
j);
}
void
adjustOwnerCountObj(
ApplyView& view,
SLE::ref accountSle,
SLE::ref objectSle,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
inline void
adjustOwnerCountObj(
ApplyView& view,
AccountID const& account,
SLE::ref objectSle,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
SLE::ref accountSle = view.peek(keylet::account(account));
adjustOwnerCountObj(view, accountSle, objectSle, amount, j);
}
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
@@ -71,7 +158,7 @@ getPseudoAccountFields();
- null pointer
*/
[[nodiscard]] bool
isPseudoAccount(SLE::const_pointer sleAcct, std::set<SField const*> const& pseudoFieldFilter = {});
isPseudoAccount(SLE::const_ref sleAcct, std::set<SField const*> const& pseudoFieldFilter = {});
/** Convenience overload that reads the account from the view. */
[[nodiscard]] inline bool

View File

@@ -36,13 +36,13 @@ checkFields(STTx const& tx, beast::Journal j);
TER
valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal j);
// Check if subject has any credential maching the given domain. If you call it
// Check if subject has any credential matching the given domain. If you call it
// in preclaim and it returns tecEXPIRED, you should call verifyValidDomain in
// doApply. This will ensure that expired credentials are deleted.
TER
validDomain(ReadView const& view, uint256 domainID, AccountID const& subject);
// This function is only called when we about to return tecNO_PERMISSION
// This function is only called when we are about to return tecNO_PERMISSION
// because all the checks for the DepositPreauth authorization failed.
TER
authorizedDepositPreauth(ReadView const& view, STVector256 const& ctx, AccountID const& dst);
@@ -58,7 +58,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j);
} // namespace credentials
// Check expired credentials and for credentials maching DomainID of the ledger
// Check expired credentials and for credentials matching DomainID of the ledger
// object
TER
verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j);

View File

@@ -6,6 +6,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTAmount.h>
@@ -17,6 +18,7 @@ template <ValidIssueType T>
TER
escrowUnlockApplyHelper(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
@@ -31,6 +33,7 @@ template <>
inline TER
escrowUnlockApplyHelper<Issue>(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
@@ -56,8 +59,13 @@ escrowUnlockApplyHelper<Issue>(
if (!view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret =
checkInsufficientReserve(view, tx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
@@ -84,6 +92,7 @@ escrowUnlockApplyHelper<Issue>(
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
@@ -161,6 +170,7 @@ template <>
inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
@@ -176,24 +186,31 @@ escrowUnlockApplyHelper<MPTIssue>(
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptIssuance(mptID);
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
{
return tecINSUFFICIENT_RESERVE;
}
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter))
if (auto const ret =
checkInsufficientReserve(view, tx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
adjustOwnerCount(view, sleDest, 1, journal);
adjustOwnerCount(view, sleDest, *sponsorSle, 1, journal);
auto mptSle = view.peek(mptKeylet);
addSponsorToLedgerEntry(mptSle, *sponsorSle);
}
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
if (!view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(view, amount);

View File

@@ -72,6 +72,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
STTx const& tx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -103,6 +104,7 @@ requireAuth(
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
STTx const& tx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
@@ -189,6 +191,7 @@ canMPTTradeAndTransfer(
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
@@ -197,6 +200,7 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
@@ -228,6 +232,7 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::ref sponsorSle,
std::uint32_t const flags);
TER
@@ -235,6 +240,7 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
beast::Journal j);
//------------------------------------------------------------------------------

View File

@@ -39,7 +39,7 @@ findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftoken
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft);
insertToken(ApplyView& view, STTx const& tx, AccountID owner, SLE::ref sponsorSle, STObject&& nft);
/** Remove the token from the owner's token directory. */
TER
@@ -107,6 +107,7 @@ tokenOfferCreatePreclaim(
TER
tokenOfferCreateApply(
ApplyView& view,
STTx const& tx,
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,

View File

@@ -5,9 +5,45 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
#include <memory>
#include <optional>
namespace xrpl {
/** Close a payment channel and return its remaining funds to the channel owner.
*
* @param slep The SLE for the PayChannel object to close.
* @param view The apply view in which ledger state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
*/
TER
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j);
/** Add two uint32_t values with saturation at UINT32_MAX.
*
* @param rules The current ledger rules used to check amendment status.
* @param lhs Left-hand operand.
* @param rhs Right-hand operand.
* @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment
* is active.
*/
uint32_t
saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs);
/** Determine whether a payment channel time field represents an expired time.
*
* @param view The apply view providing the parent close time and rules.
* @param timeField The optional expiry timestamp (seconds since the XRP
* Ledger epoch). If empty, the function returns false.
* @return @c true if @p timeField is set and the indicated time is
* in the past relative to the view's parent close time;
* @c false otherwise.
*/
bool
isChannelExpired(ApplyView const& view, std::optional<std::uint32_t> timeField);
} // namespace xrpl

View File

@@ -149,6 +149,7 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
SLE::ref sponsorSle,
beast::Journal j);
[[nodiscard]] TER
@@ -229,6 +230,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,

View File

@@ -0,0 +1,137 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFlags.h>
#include <expected>
namespace xrpl {
inline bool
isReserveSponsored(STTx const& tx)
{
return (tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve) != 0u;
}
inline bool
isSponsorReserveCoSigning(STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsorSignature))
return false;
return isReserveSponsored(tx);
}
inline std::optional<AccountID>
getTxReserveSponsorAccountID(STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && isReserveSponsored(tx))
{
return tx.getAccountID(sfSponsor);
}
return {};
}
inline std::expected<SLE::pointer, TER>
getTxReserveSponsor(ApplyView& view, STTx const& tx)
{
auto const sponsorID = getTxReserveSponsorAccountID(tx);
if (sponsorID)
{
auto sle = view.peek(keylet::account(*sponsorID));
// already checked in Transactor::checkSponsor
if (!sle)
return std::unexpected(tecINTERNAL);
return sle;
}
return SLE::pointer();
}
inline std::expected<SLE::const_pointer, TER>
getTxReserveSponsor(ReadView const& view, STTx const& tx)
{
auto const sponsorID = getTxReserveSponsorAccountID(tx);
if (sponsorID)
{
auto sle = view.read(keylet::account(*sponsorID));
// already checked in Transactor::checkSponsor
if (!sle)
return std::unexpected(tecINTERNAL);
return sle;
}
return SLE::pointer();
}
inline std::optional<AccountID>
getLedgerEntryReserveSponsorAccountID(SLE::const_ref sle, SF_ACCOUNT const& field = sfSponsor)
{
if (sle->isFieldPresent(field))
return sle->getAccountID(field);
return {};
}
inline SLE::pointer
getLedgerEntryReserveSponsor(
ApplyView& view,
SLE::const_ref sle,
SF_ACCOUNT const& field = sfSponsor)
{
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.peek(keylet::account(*sponsorID));
return {};
}
inline SLE::const_pointer
getLedgerEntryReserveSponsor(
ReadView const& view,
SLE::const_ref sle,
SF_ACCOUNT const& field = sfSponsor)
{
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.read(keylet::account(*sponsorID));
return {};
}
inline void
addSponsorToLedgerEntry(
SLE::ref sle,
SLE::const_ref sponsorSle,
SF_ACCOUNT const& field = sfSponsor)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"addSponsorToLedgerEntry : Invalid field to the LedgerEntry");
if (sponsorSle)
sle->setAccountID(field, sponsorSle->getAccountID(sfAccount));
}
inline void
removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field = sfSponsor)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"removeSponsorFromLedgerEntry : Invalid field to the LedgerEntry");
if (sle->isFieldPresent(field))
sle->makeFieldAbsent(field);
}
// namespace sponsor
// {
// // Accessing the ledger to check if provided sponsor is valid.
// [[nodiscard]] TER
// valid(ReadView const& view, STTx const& tx, beast::Journal j)
// {
// }
// }
} // namespace xrpl

View File

@@ -231,6 +231,7 @@ canAddHolding(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -239,6 +240,7 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal);
@@ -299,6 +301,7 @@ accountSend(
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle = {},
WaiveTransferFee waiveFee = WaiveTransferFee::No,
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
@@ -316,6 +319,7 @@ accountSendMulti(
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
[[nodiscard]] TER

View File

@@ -102,25 +102,32 @@ getAPIVersionNumber(json::Value const& jv, bool betaEnabled)
json::Value const maxVersion(
betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion);
if (jv.isObject())
if (!jv.isObject() || !jv.isMember(jss::api_version))
return RPC::kApiVersionIfUnspecified;
try
{
if (jv.isMember(jss::api_version))
auto const& rawVersion = jv[jss::api_version];
switch (rawVersion.type())
{
auto const specifiedVersion = jv[jss::api_version];
if (!specifiedVersion.isInt() && !specifiedVersion.isUInt())
{
return RPC::kApiInvalidVersion;
case json::ValueType::Int:
if (rawVersion.asInt() < 0)
return RPC::kApiInvalidVersion;
[[fallthrough]];
case json::ValueType::UInt: {
auto const apiVersion = rawVersion.asUInt();
if (apiVersion < kMinVersion || apiVersion > maxVersion)
return RPC::kApiInvalidVersion;
return apiVersion;
}
auto const specifiedVersionInt = specifiedVersion.asInt();
if (specifiedVersionInt < kMinVersion || specifiedVersionInt > maxVersion)
{
default:
return RPC::kApiInvalidVersion;
}
return specifiedVersionInt;
}
}
return RPC::kApiVersionIfUnspecified;
catch (...)
{
return RPC::kApiInvalidVersion;
}
}
} // namespace RPC

View File

@@ -33,17 +33,6 @@ struct Fees
: base(base), reserve(reserve), increment(increment)
{
}
/** Returns the account reserve given the owner count, in drops.
The reserve is calculated as the reserve base plus
the reserve increment times the number of increments.
*/
[[nodiscard]] XRPAmount
accountReserve(std::size_t ownerCount) const
{
return reserve + ownerCount * increment;
}
};
} // namespace xrpl

View File

@@ -151,6 +151,10 @@ static TicketT const kTicket{};
Keylet
signers(AccountID const& account) noexcept;
/** A Sponsorship */
Keylet
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept;
/** A Check */
/** @{ */
Keylet

View File

@@ -203,7 +203,11 @@ enum LedgerEntryType : std::uint16_t {
LEDGER_OBJECT(Loan, \
LSF_FLAG(lsfLoanDefault, 0x00010000) \
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \
\
LEDGER_OBJECT(Sponsorship, \
LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \
LSF_FLAG(lsfSponsorshipRequireSignForReserve, 0x00020000))
// clang-format on

View File

@@ -220,6 +220,7 @@ enum TERcodes : TERUnderlyingType {
// create a pseudo-account
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
terLOCKED, // MPT is locked
terNO_SPONSORSHIP, // No sponsorship found
};
//------------------------------------------------------------------------------
@@ -358,6 +359,7 @@ enum TECcodes : TERUnderlyingType {
tecLIMIT_EXCEEDED = 195,
tecPSEUDO_ACCOUNT = 196,
tecPRECISION_LOSS = 197,
tecNO_SPONSOR_PERMISSION = 198,
};
//------------------------------------------------------------------------------

View File

@@ -102,7 +102,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TRANSACTION(Payment, \
TF_FLAG(tfNoRippleDirect, 0x00010000) \
TF_FLAG(tfPartialPayment, 0x00020000) \
TF_FLAG(tfLimitQuality, 0x00040000), \
TF_FLAG(tfLimitQuality, 0x00040000) \
TF_FLAG(tfSponsorCreatedAccount, 0x00080000), \
MASK_ADJ(0)) \
\
TRANSACTION(TrustSet, \
@@ -214,6 +215,20 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfLoanDefault, 0x00010000) \
TF_FLAG(tfLoanImpair, 0x00020000) \
TF_FLAG(tfLoanUnimpair, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipSet, \
TF_FLAG(tfSponsorshipSetRequireSignForFee, 0x00010000) \
TF_FLAG(tfSponsorshipClearRequireSignForFee, 0x00020000) \
TF_FLAG(tfSponsorshipSetRequireSignForReserve, 0x00040000) \
TF_FLAG(tfSponsorshipClearRequireSignForReserve, 0x00080000) \
TF_FLAG(tfDeleteObject, 0x00100000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipTransfer, \
TF_FLAG(tfSponsorshipEnd, 0x00000001) \
TF_FLAG(tfSponsorshipCreate, 0x00000002) \
TF_FLAG(tfSponsorshipReassign, 0x00000004), \
MASK_ADJ(0))
// clang-format on
@@ -338,6 +353,9 @@ getAllTxFlags()
inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
inline constexpr FlagValue tfTrustSetPermissionMask =
~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
inline constexpr FlagValue tfSponsorshipSetPermissionMask =
~(tfUniversal | tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve |
tfSponsorshipClearRequireSignForFee | tfSponsorshipClearRequireSignForReserve);
// MPTokenIssuanceCreate MutableFlags:
// Indicating specific fields or flags may be changed after issuance.
@@ -445,6 +463,35 @@ getAsfFlagMap()
#pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP")
#pragma pop_macro("ACCOUNTSET_FLAGS")
#pragma push_macro("SPONSOR_FLAGS")
#pragma push_macro("SPONSOR_FLAG_TO_VALUE")
#pragma push_macro("SPONSOR_FLAG_TO_MAP")
// Sponsor Flag values
#define SPONSOR_FLAGS(SPF_FLAG) \
SPF_FLAG(spfSponsorFee, 1) \
SPF_FLAG(spfSponsorReserve, 2)
#define SPONSOR_FLAG_TO_VALUE(name, value) inline constexpr FlagValue name = value;
#define SPONSOR_FLAG_TO_MAP(name, value) {#name, value},
SPONSOR_FLAGS(SPONSOR_FLAG_TO_VALUE)
inline std::map<std::string, FlagValue> const&
getspfFlagMap()
{
static std::map<std::string, FlagValue> const flags = {SPONSOR_FLAGS(SPONSOR_FLAG_TO_MAP)};
return flags;
}
#undef SPONSOR_FLAG_TO_VALUE
#undef SPONSOR_FLAG_TO_MAP
#undef SPONSOR_FLAGS
#pragma pop_macro("SPONSOR_FLAG_TO_VALUE")
#pragma pop_macro("SPONSOR_FLAG_TO_MAP")
#pragma pop_macro("SPONSOR_FLAGS")
} // namespace xrpl
// NOLINTEND(readability-identifier-naming)

View File

@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(Sponsor, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)

View File

@@ -150,6 +150,9 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfAMMID, SoeOptional}, // pseudo-account designator
{sfVaultID, SoeOptional}, // pseudo-account designator
{sfLoanBrokerID, SoeOptional}, // pseudo-account designator
{sfSponsoredOwnerCount, SoeDefault},
{sfSponsoringOwnerCount, SoeDefault},
{sfSponsoringAccountCount,SoeDefault},
}))
/** A ledger object which contains a list of object identifiers.
@@ -286,6 +289,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
{sfHighNode, SoeOptional},
{sfHighQualityIn, SoeOptional},
{sfHighQualityOut, SoeOptional},
{sfHighSponsor, SoeOptional},
{sfLowSponsor, SoeOptional},
}))
/** The ledger object which lists the network's fee settings.
@@ -607,5 +612,20 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfLoanScale, SoeDefault},
}))
/** A ledger object representing a sponsorship.
\sa keylet::sponsor
*/
LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeRequired},
{sfSponsee, SoeRequired},
{sfFeeAmount, SoeOptional},
{sfMaxFee, SoeOptional},
{sfReserveCount, SoeDefault},
{sfOwnerNode, SoeRequired},
{sfSponseeNode, SoeRequired},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE

View File

@@ -47,3 +47,9 @@ PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547)
/** This permission grants the delegated account the ability to unlock MPToken. */
PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548)
/** This permission grants the delegated account the ability to set SponsorFee. */
PERMISSION(SponsorFee, ttSPONSORSHIP_SET, 65549)
/** This permission grants the delegated account the ability to set SponsorReserve. */
PERMISSION(SponsorReserve, ttSPONSORSHIP_SET, 65550)

View File

@@ -113,6 +113,11 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 69)
TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 70)
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 71)
TYPED_SFIELD(sfReserveCount, UINT32, 72)
TYPED_SFIELD(sfSponsorFlags, UINT32, 73)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -146,6 +151,7 @@ TYPED_SFIELD(sfSubjectNode, UINT64, 28)
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::kSmdBaseTen|SField::kSmdDefault)
TYPED_SFIELD(sfVaultNode, UINT64, 30)
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
TYPED_SFIELD(sfSponseeNode, UINT64, 32)
// 128-bit
TYPED_SFIELD(sfEmailHash, UINT128, 1)
@@ -206,6 +212,7 @@ TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
SField::kSmdPseudoAccount | SField::kSmdDefault)
TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
TYPED_SFIELD(sfObjectID, UINT256, 40)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -265,6 +272,8 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
// variable length (common)
TYPED_SFIELD(sfPublicKey, VL, 1)
@@ -325,6 +334,11 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
TYPED_SFIELD(sfCounterparty, ACCOUNT, 26)
TYPED_SFIELD(sfSponsor, ACCOUNT, 27)
TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28)
TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29)
TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30)
TYPED_SFIELD(sfSponsee, ACCOUNT, 31)
// vector of 256-bit
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever)
@@ -389,6 +403,7 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
UNTYPED_SFIELD(sfBook, OBJECT, 36)
UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning)
UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::kSmdDefault, SField::kNotSigning)
// array of objects (common)
// ARRAY/1 is reserved for end of array

View File

@@ -1077,6 +1077,35 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
{sfAmount, SoeRequired, SoeMptSupported},
}))
/** This transaction transfer sponsorship */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
#endif
TRANSACTION(ttSPONSORSHIP_TRANSFER, 85, SponsorshipTransfer,
Delegation::Delegable,
featureSponsor,
NoPriv,
({
{sfObjectID, SoeOptional},
{sfSponsee, SoeOptional},
}))
/** This transaction create sponsorship object */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
#endif
TRANSACTION(ttSPONSORSHIP_SET, 86, SponsorshipSet,
Delegation::Delegable,
featureSponsor,
NoPriv,
({
{sfCounterpartySponsor, SoeOptional},
{sfSponsee, SoeOptional},
{sfFeeAmount, SoeOptional},
{sfMaxFee, SoeOptional},
{sfReserveCount, SoeOptional},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -552,6 +552,9 @@ JSS(source_account); // in: PathRequest, RipplePathFind
JSS(source_amount); // in: PathRequest, RipplePathFind
JSS(source_currencies); // in: PathRequest, RipplePathFind
JSS(source_tag); // out: AccountChannels
JSS(sponsee); // in: LedgerEntry
JSS(sponsor); // in: LedgerEntry
JSS(sponsored); // in: AccountObjects
JSS(stand_alone); // out: NetworkOPs
JSS(standard_deviation); // out: get_aggregate_price
JSS(start); // in: TxHistory

View File

@@ -518,6 +518,78 @@ public:
{
return this->sle_->isFieldPresent(sfLoanBrokerID);
}
/**
* @brief Get sfSponsoredOwnerCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoredOwnerCount() const
{
if (hasSponsoredOwnerCount())
return this->sle_->at(sfSponsoredOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoredOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoredOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoredOwnerCount);
}
/**
* @brief Get sfSponsoringOwnerCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringOwnerCount() const
{
if (hasSponsoringOwnerCount())
return this->sle_->at(sfSponsoringOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoringOwnerCount);
}
/**
* @brief Get sfSponsoringAccountCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringAccountCount() const
{
if (hasSponsoringAccountCount())
return this->sle_->at(sfSponsoringAccountCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringAccountCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringAccountCount() const
{
return this->sle_->isFieldPresent(sfSponsoringAccountCount);
}
};
/**
@@ -819,6 +891,39 @@ public:
return *this;
}
/**
* @brief Set sfSponsoredOwnerCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoredOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoredOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringOwnerCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringAccountCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringAccountCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringAccountCount] = value;
return *this;
}
/**
* @brief Build and return the completed AccountRoot wrapper.
* @param index The ledger entry index.

View File

@@ -243,6 +243,54 @@ public:
{
return this->sle_->isFieldPresent(sfHighQualityOut);
}
/**
* @brief Get sfHighSponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getHighSponsor() const
{
if (hasHighSponsor())
return this->sle_->at(sfHighSponsor);
return std::nullopt;
}
/**
* @brief Check if sfHighSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHighSponsor() const
{
return this->sle_->isFieldPresent(sfHighSponsor);
}
/**
* @brief Get sfLowSponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getLowSponsor() const
{
if (hasLowSponsor())
return this->sle_->at(sfLowSponsor);
return std::nullopt;
}
/**
* @brief Check if sfLowSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasLowSponsor() const
{
return this->sle_->isFieldPresent(sfLowSponsor);
}
};
/**
@@ -410,6 +458,28 @@ public:
return *this;
}
/**
* @brief Set sfHighSponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setHighSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHighSponsor] = value;
return *this;
}
/**
* @brief Set sfLowSponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setLowSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfLowSponsor] = value;
return *this;
}
/**
* @brief Build and return the completed RippleState wrapper.
* @param index The ledger entry index.

View File

@@ -0,0 +1,344 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class SponsorshipBuilder;
/**
* @brief Ledger Entry: Sponsorship
*
* Type: ltSPONSORSHIP (0x0090)
* RPC Name: sponsorship
*
* Immutable wrapper around SLE providing type-safe field access.
* Use SponsorshipBuilder to construct new ledger entries.
*/
class Sponsorship : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltSPONSORSHIP;
/**
* @brief Construct a Sponsorship ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Sponsorship(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfOwner (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getOwner() const
{
return this->sle_->at(sfOwner);
}
/**
* @brief Get sfSponsee (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getSponsee() const
{
return this->sle_->at(sfSponsee);
}
/**
* @brief Get sfFeeAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
return this->sle_->at(sfFeeAmount);
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->sle_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
return this->sle_->at(sfMaxFee);
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->sle_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfReserveCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getReserveCount() const
{
if (hasReserveCount())
return this->sle_->at(sfReserveCount);
return std::nullopt;
}
/**
* @brief Check if sfReserveCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasReserveCount() const
{
return this->sle_->isFieldPresent(sfReserveCount);
}
/**
* @brief Get sfOwnerNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfSponseeNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getSponseeNode() const
{
return this->sle_->at(sfSponseeNode);
}
};
/**
* @brief Builder for Sponsorship ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses STObject internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class SponsorshipBuilder : public LedgerEntryBuilderBase<SponsorshipBuilder>
{
public:
/**
* @brief Construct a new SponsorshipBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param owner The sfOwner field value.
* @param sponsee The sfSponsee field value.
* @param ownerNode The sfOwnerNode field value.
* @param sponseeNode The sfSponseeNode field value.
*/
SponsorshipBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& sponsee,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& sponseeNode)
: LedgerEntryBuilderBase<SponsorshipBuilder>(ltSPONSORSHIP)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setOwner(owner);
setSponsee(sponsee);
setOwnerNode(ownerNode);
setSponseeNode(sponseeNode);
}
/**
* @brief Construct a SponsorshipBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
SponsorshipBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltSPONSORSHIP)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Set sfPreviousTxnID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfOwner (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfReserveCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setReserveCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfReserveCount] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Set sfSponseeNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponseeNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfSponseeNode] = value;
return *this;
}
/**
* @brief Build and return the completed Sponsorship wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
Sponsorship
build(uint256 const& index)
{
return Sponsorship{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,290 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipSetBuilder;
/**
* @brief Transaction: SponsorshipSet
*
* Type: ttSPONSORSHIP_SET (86)
* Delegable: Delegation::Delegable
* Amendment: featureSponsor
* Privileges: NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipSetBuilder to construct new transactions.
*/
class SponsorshipSet : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_SET;
/**
* @brief Construct a SponsorshipSet transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipSet(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSet");
}
}
// Transaction-specific field getters
/**
* @brief Get sfCounterpartySponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getCounterpartySponsor() const
{
if (hasCounterpartySponsor())
{
return this->tx_->at(sfCounterpartySponsor);
}
return std::nullopt;
}
/**
* @brief Check if sfCounterpartySponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasCounterpartySponsor() const
{
return this->tx_->isFieldPresent(sfCounterpartySponsor);
}
/**
* @brief Get sfSponsee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
/**
* @brief Get sfFeeAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
{
return this->tx_->at(sfFeeAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->tx_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
{
return this->tx_->at(sfMaxFee);
}
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->tx_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfReserveCount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getReserveCount() const
{
if (hasReserveCount())
{
return this->tx_->at(sfReserveCount);
}
return std::nullopt;
}
/**
* @brief Check if sfReserveCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasReserveCount() const
{
return this->tx_->isFieldPresent(sfReserveCount);
}
};
/**
* @brief Builder for SponsorshipSet transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipSetBuilder : public TransactionBuilderBase<SponsorshipSetBuilder>
{
public:
/**
* @brief Construct a new SponsorshipSetBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipSetBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipSetBuilder>(ttSPONSORSHIP_SET, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipSetBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipSetBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_SET)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSetBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfCounterpartySponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setCounterpartySponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfCounterpartySponsor] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfReserveCount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setReserveCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfReserveCount] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipSet wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipSet
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipSet{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,179 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipTransferBuilder;
/**
* @brief Transaction: SponsorshipTransfer
*
* Type: ttSPONSORSHIP_TRANSFER (85)
* Delegable: Delegation::Delegable
* Amendment: featureSponsor
* Privileges: NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipTransferBuilder to construct new transactions.
*/
class SponsorshipTransfer : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_TRANSFER;
/**
* @brief Construct a SponsorshipTransfer transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipTransfer(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransfer");
}
}
// Transaction-specific field getters
/**
* @brief Get sfObjectID (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getObjectID() const
{
if (hasObjectID())
{
return this->tx_->at(sfObjectID);
}
return std::nullopt;
}
/**
* @brief Check if sfObjectID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasObjectID() const
{
return this->tx_->isFieldPresent(sfObjectID);
}
/**
* @brief Get sfSponsee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
};
/**
* @brief Builder for SponsorshipTransfer transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipTransferBuilder : public TransactionBuilderBase<SponsorshipTransferBuilder>
{
public:
/**
* @brief Construct a new SponsorshipTransferBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipTransferBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipTransferBuilder>(ttSPONSORSHIP_TRANSFER, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipTransferBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipTransferBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_TRANSFER)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransferBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfObjectID (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setObjectID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfObjectID] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipTransfer wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipTransfer
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipTransfer{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -1,6 +1,7 @@
#pragma once
#include <xrpl/basics/CountedObject.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h>
@@ -26,6 +27,19 @@ public:
};
/** Manages a client's subscription to data feeds.
*
* An InfoSub holds a non-owning reference to its `Source` (typically the
* process-wide `NetworkOPsImp`). The destructor reaches back into the
* `Source` to remove this subscriber from every server-side subscription
* map.
*
* @note Lifetime contract: every `InfoSub` instance MUST be destroyed
* before the backing `Source`. NetworkOPsImp shutdown drops all
* subscriber strong refs before its own teardown to satisfy this.
* @note Thread-safety: per-instance state is guarded by `lock_`. The
* destructor reads tracking sets without taking `lock_` because
* the strong-pointer ref-count is zero at destruction time, so
* no other thread can be calling the public mutators.
*/
class InfoSub : public CountedObject<InfoSub>
{
@@ -117,8 +131,43 @@ public:
virtual bool
subBook(ref ispListener, Book const&) = 0;
/**
* Remove a book subscription for a live subscriber.
*
* Clears the book from the subscriber's own tracking set
* (InfoSub::bookSubscriptions_) and then removes the server-side
* entry from subBook_. Call this from RPC unsubscribe handlers.
*
* @param ispListener The subscriber requesting removal.
* @param book The order book to unsubscribe from.
* @return true if the entry was present and removed, false if the
* subscriber was not subscribed to @p book.
*
* @note Thread-safety: acquires subLock_ internally.
* @note Do NOT call from ~InfoSub(). Use unsubBookInternal instead
* to avoid a redundant write-back to bookSubscriptions_ on a
* partially-destroyed object.
*/
virtual bool
unsubBook(std::uint64_t uListener, Book const&) = 0;
unsubBook(ref ispListener, Book const&) = 0;
/**
* Remove a book subscription during InfoSub teardown.
*
* Removes only the server-side entry from subBook_. Does NOT touch
* InfoSub::bookSubscriptions_ because the InfoSub is being destroyed.
* Called by ~InfoSub() for each book in bookSubscriptions_.
*
* @param uListener The sequence number of the subscriber being torn down.
* @param book The order book entry to remove.
* @return true if the entry was present and removed, false otherwise
* (e.g., already removed by a concurrent RPC unsubscribe).
*
* @note Thread-safety: acquires subLock_ internally.
*/
virtual bool
unsubBookInternal(std::uint64_t uListener, Book const&) = 0;
virtual bool
subTransactions(ref ispListener) = 0;
@@ -158,6 +207,13 @@ public:
addRpcSub(std::string const& strUrl, ref rspEntry) = 0;
virtual bool
tryRemoveRpcSub(std::string const& strUrl) = 0;
/** Journal used by InfoSub for diagnostics that occur after the
* owning subsystem (e.g. application-level Logs) is the only
* surviving sink — primarily destructor-time cleanup failures.
*/
[[nodiscard]] virtual beast::Journal const&
journal() const = 0;
};
public:
@@ -184,6 +240,31 @@ public:
void
deleteSubAccountInfo(AccountID const& account, bool rt);
/** Record that this subscriber is following @p book.
*
* Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a
* matching unsubBook for every book this subscriber is tracking,
* keeping per-subscriber state symmetric with the server-side map.
*
* @param book The order book this subscriber has just subscribed to.
* @note Idempotent: re-inserting an already-tracked book is a no-op.
* @note Thread-safe: takes InfoSub::lock_.
*/
void
insertBookSubscription(Book const& book);
/** Stop tracking @p book for this subscriber.
*
* Called by the unsubscribe RPC handler so that the book is not
* re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription.
*
* @param book The order book to forget.
* @note No-op if @p book was not previously inserted.
* @note Thread-safe: takes InfoSub::lock_.
*/
void
deleteBookSubscription(Book const& book);
// return false if already subscribed to this account
bool
insertSubAccountHistory(AccountID const& account);
@@ -217,6 +298,7 @@ private:
std::shared_ptr<InfoSubRequest> request_;
std::uint64_t seq_;
hash_set<AccountID> accountHistorySubscriptions_;
hash_set<Book> bookSubscriptions_;
unsigned int apiVersion_ = 0;
static int

View File

@@ -249,6 +249,19 @@ public:
virtual void
stateAccounting(json::Value& obj) = 0;
/** Total number of (book, subscriber) entries currently tracked.
*
* Counts every weak_ptr stored across every book in subBook_, NOT the
* number of distinct subscribers and NOT the number of distinct
* books: a single subscriber following N books contributes N entries.
*
* @note Diagnostic accessor; intended for tests and operator visibility
* into per-book subscription state. The returned value is a
* snapshot under the subscription lock.
*/
virtual std::size_t
getBookSubscribersCount() = 0;
};
} // namespace xrpl

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
@@ -108,6 +109,20 @@ struct PreflightResult;
// Needed for preflight specialization
class Change;
enum class FeePayerType {
Account,
Delegate,
SponsorCoSigned,
SponsorPreFunded,
};
struct FeePayer
{
Keylet entry;
SF_AMOUNT const& balanceField;
FeePayerType type{FeePayerType::Account};
};
class Transactor
{
protected:
@@ -224,6 +239,9 @@ public:
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static NotTEC
checkSponsor(ReadView const& view, STTx const& tx);
/////////////////////////////////////////////////////
// Interface used by AccountDelete
@@ -356,6 +374,9 @@ private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
static FeePayer
getFeePayer(ReadView const& view, STTx const& tx);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER

View File

@@ -14,6 +14,7 @@
#include <xrpl/tx/invariants/NFTInvariant.h>
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
#include <xrpl/tx/invariants/VaultInvariant.h>
#include <cstdint>
@@ -415,7 +416,9 @@ using InvariantChecks = std::tuple<
ValidVault,
ValidMPTPayment,
ValidAmounts,
ValidMPTTransfer>;
ValidMPTTransfer,
SponsorshipOwnerCountsMatch,
SponsorshipAccountCountMatchesField>;
/**
* @brief get a tuple of all invariant checks

View File

@@ -0,0 +1,56 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
namespace xrpl {
/**
* @brief Invariant: Sponsored owner counts are balanced.
*
* The following check is made for every transaction:
* - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals
* the sum of all per-account deltas of `sfSponsoringOwnerCount`.
* - Account OwnerCount must be greater than or equal to SponsoredOwnerCount.
*/
class SponsorshipOwnerCountsMatch
{
std::int64_t deltaSponsoredOwnerCount_ = 0;
std::int64_t deltaSponsoringOwnerCount_ = 0;
std::int64_t deltaSponsoredObjectOwnerCount_ = 0;
std::uint64_t invalidOwnerCountLessThanSponsoredOwnerCount_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
/**
* @brief Invariant: Sponsoring account relationships tracked consistently.
*
* The following check is made for every transaction:
* - The net delta of `sfSponsoringAccountCount` across all accounts equals
* the net delta of the count of ltACCOUNT_ROOT entries having
* `sfSponsor` present (presence transitions only: add/remove).
*/
class SponsorshipAccountCountMatchesField
{
std::int64_t deltaSponsoringAccountCount_ = 0;
std::int64_t deltaSponsorFieldPresence_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
} // namespace xrpl

View File

@@ -104,7 +104,10 @@ public:
send(Args&&... args)
{
return accountSend(
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
std::forward<Args>(args)...,
SLE::pointer(),
WaiveTransferFee::Yes,
AllowMPTOverflow::Yes);
}
[[nodiscard]] bool

View File

@@ -224,7 +224,8 @@ template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
return accountSend(
std::forward<Args>(args)..., SLE::pointer(), WaiveTransferFee::No, AllowMPTOverflow::Yes);
}
template <StepAmount TIn, StepAmount TOut>

View File

@@ -100,6 +100,7 @@ public:
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const account,
AccountID const& ammAccount,
@@ -134,6 +135,7 @@ public:
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
AccountID const& account,
@@ -177,6 +179,7 @@ private:
std::pair<TER, STAmount>
withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -202,6 +205,7 @@ private:
std::pair<TER, STAmount>
equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -227,6 +231,7 @@ private:
std::pair<TER, STAmount>
equalWithdrawLimit(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -249,6 +254,7 @@ private:
std::pair<TER, STAmount>
singleWithdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -270,6 +276,7 @@ private:
std::pair<TER, STAmount>
singleWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -292,6 +299,7 @@ private:
std::pair<TER, STAmount>
singleWithdrawEPrice(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,

View File

@@ -22,6 +22,12 @@ public:
{
}
static uint32_t
calculateOracleReserve(std::size_t count)
{
return count > 5 ? 2 : 1;
}
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -0,0 +1,49 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SponsorshipSet : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
static TER
deleteSponsorship(ApplyView& view, SLE::ref sle, beast::Journal j);
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -0,0 +1,43 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SponsorshipTransfer : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SponsorshipTransfer(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -63,7 +63,7 @@ public:
beast::Journal const& j) override;
static std::expected<MPTID, TER>
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
create(ApplyView& view, STTx const& tx, beast::Journal journal, MPTCreateArgs const& args);
};
} // namespace xrpl

View File

@@ -71,7 +71,7 @@ if [ ! -e "${target}" ]; then
fi
EOF
COPY nix/docker/check-tools.sh /tmp/check-tools.sh
COPY bin/check-tools.sh /tmp/check-tools.sh
RUN /tmp/check-tools.sh
# Sanity-check that the g++/clang++ are able to build binaries, including sanitizer-instrumented ones.
@@ -93,7 +93,7 @@ RUN if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
# Sanity-check that the built binaries run correctly in the vanilla base image, with the necessary sanitizer runtime libraries installed.
COPY nix/docker/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
COPY bin/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
COPY nix/docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh
COPY --from=final /tmp/bins /tmp/bins

90
nix/docker/README.md Normal file
View File

@@ -0,0 +1,90 @@
# Nix CI Docker images
This directory builds the Docker images used by xrpld's Linux CI. Each image
bundles the **exact same toolchain that the Nix development shell provides**
(see [`docs/build/nix.md`](../../docs/build/nix.md)), so what runs in CI matches
what developers get locally from `nix develop`.
The toolchain (CMake, Ninja, Conan, GCC, Clang, clang-tidy, the
sanitizer/coverage tools, …) is defined in [`nix/packages.nix`](../packages.nix)
and assembled for CI by [`nix/ci-env.nix`](../ci-env.nix). The Docker build
turns that Nix environment into an ordinary container image layered on top of a
conventional base image (Ubuntu, Debian, RHEL, or `nixos/nix`).
## Images
The images are built by the [`build-nix-images.yml`](../../.github/workflows/build-nix-images.yml)
workflow and pushed to `ghcr.io/xrplf/xrpld/nix-<distro>`. The `<distro>` is
selected through the `BASE_IMAGE` build argument; the base images are the
**oldest supported version** of each distribution we target:
| Image | `BASE_IMAGE` | Notes |
| ------------ | -------------------------------------------- | -------------------------------------------------- |
| `nix-nixos` | `nixos/nix:latest` | Build/lint only; binaries are not run (see below). |
| `nix-ubuntu` | `ubuntu:20.04` | Oldest supported Ubuntu (glibc 2.31). |
| `nix-debian` | `debian:bookworm` | |
| `nix-rhel` | `registry.access.redhat.com/ubi9/ubi:latest` | |
All images carry the full toolchain on `PATH` (via `/nix/ci-env/bin`) plus the
CA bundle shipped in the Nix environment, so HTTPS clients (git, curl, Conan)
work without `ca-certificates` being installed in the base image.
## Build stages
[`Dockerfile`](./Dockerfile) is a multi-stage build:
1. **`builder`** — On a `nixos/nix` builder, evaluate the flake and build the
CI environment (`nix/ci-env.nix`). The resulting Nix store closure (the
complete set of store paths the toolchain depends on) is copied into a
staging directory.
2. **`final`** — Start from `BASE_IMAGE`, copy in the Nix store closure and the
`ci-env` symlink tree, and wire up `PATH` and the CA bundle. It then:
- installs the dynamic linker if the base image lacks one (see
[How libc is handled](#how-libc-is-handled)),
- runs [`bin/check-tools.sh`](../../bin/check-tools.sh) to verify every
expected tool is present and runnable, and
- compiles the C++ test programs in
[`test_files/`](./test_files) with both `g++` and `clang++`, and sanitizers.
3. **`tester`** — Start again from a clean `BASE_IMAGE` (no Nix toolchain),
install only the sanitizer runtime libraries
([`install-sanitizer-libs.sh`](./install-sanitizer-libs.sh)), and run the
binaries compiled in `final`. This proves the binaries built with the Nix
toolchain actually run on a vanilla base image. On `nixos/nix` this step is
skipped (the binaries are patched for a conventional FHS loader).
4. **Output** — The final image is gated on the tester succeeding: it copies a
sentinel file out of `tester`, so a failed test run fails the whole build.
## How libc is handled
The goal is for binaries built in these images to run on the **oldest supported
base image** (Ubuntu 20.04, glibc 2.31) and newer — without the developer's Nix
toolchain being present at runtime. Two pieces make that work:
- **Compilers linked against an old glibc.** The Nix CI environment does not use
nixpkgs' current glibc. Instead it pins a 2020 nixpkgs snapshot whose primary
glibc is **2.31** (matching Ubuntu 20.04), via the `nixpkgs-custom-glibc`
flake input. GCC, Clang, binutils and compiler-rt are all rebuilt/wrapped
against this custom glibc (see [`nix/ci-env.nix`](../ci-env.nix)). As a result
the libraries they emit (`libstdc++`, `libgcc_s`, the sanitizer runtimes)
reference only symbols available in glibc 2.31.
- **An expected dynamic linker in the image.**
Binaries built in Nix environments reference a dynamic linker from Nix store paths, which won't be present in the base image. However,
[`loader-path.sh`](./loader-path.sh) reports the expected loader path for the
current architecture, so we can patch the binaries to use the correct loader.
The build then verifies all of this end to end: the test programs in
`test_files/` (a regular binary plus ASan/TSan/UBSan variants) are compiled in
`final`, their `PT_INTERP` is patched to the target loader, and they are run in
the clean `tester` stage to confirm each emits the expected sanitizer
diagnostic on a stock base image.
## Files
| File | Purpose |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
| [`./test_files/`](./test_files) | C++ sources and scripts to compile and run the sanitizer smoke tests. |
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |

View File

@@ -1,38 +0,0 @@
#!/bin/bash
# Verify that every tool expected in the Nix CI env is present and runnable.
set -euo pipefail
ccache --version
clang --version
clang++ --version
clang-format --version
cmake --version
conan --version
curl --version
doxygen --version
file --version
g++ --version
gcc --version
gcov --version
gcovr --version
gh --version
git --version
git-cliff --version
gpg --version
less --version
make --version
mold --version
netstat --version
ninja --version
perl --version
pkg-config --version
pre-commit --version
python3 --version
run-clang-tidy --help
vim --version
# A simple test to verify that git can clone a repository over HTTPS
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
tmp_clone="$(mktemp -d)"
git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions"
rm -rf "${tmp_clone}"

View File

@@ -9,6 +9,7 @@ in
{
commonPackages = with pkgs; [
ccache
clangbuildanalyzer
cmake
conan
curlMinimal # needed for codecov/codecov-action
@@ -18,8 +19,10 @@ in
gh
git
git-cliff
git-lfs
gnumake
gnupg # needed for signing commits & codecov/codecov-action
graphviz
llvmPackages_22.clang-tools
less # needed for git diff
mold
@@ -32,5 +35,6 @@ in
python3
runClangTidy
vim
zip
];
}

View File

@@ -15,7 +15,6 @@ package/
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB)
xrpld.logrotate logrotate config (installed to /etc/logrotate.d/xrpld)
update-xrpld auto-update script (installed to /usr/libexec/xrpld/, run by update-xrpld.timer)
```
## Prerequisites

View File

@@ -114,10 +114,11 @@ VER_BASE="${VERSION%%-*}"
VER_SUFFIX="${VERSION#*-}"
[[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX=""
# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). The RPM
# Release field forbids '-', and the convention here is single-token suffixes
# like b1 or rc2. Fail early with a clear message rather than letting either
# rpmbuild blow up or silently mangling dashes into dots.
# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). Neither an
# RPM Version nor a Debian upstream version may contain '-' (it's the NVR /
# version-revision separator), and the convention here is single-token
# suffixes like b1 or rc2. Fail early with a clear message rather than letting
# the package tooling blow up or silently mangle dashes.
if [[ "${VER_SUFFIX}" == *-* ]]; then
echo "build_pkg.sh: multi-segment pre-release in VERSION='${VERSION}' (suffix '${VER_SUFFIX}')." >&2
echo "Use single-token suffixes like 3.2.0-b1 or 3.2.0-rc2." >&2
@@ -142,9 +143,6 @@ stage_common() {
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
cp "${SHARED}/update-xrpld" "${dest}/update-xrpld"
cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service"
cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer"
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
}
@@ -156,20 +154,18 @@ build_rpm() {
cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec"
stage_common "${topdir}/SOURCES"
# RPM Version can't contain '-'. A pre-release goes in Release with a
# leading "0." so 3.2.0-b1 sorts before the final 3.2.0-<pkg_release>.
# The order is "0.<pkg_release>.<suffix>" (e.g. 0.1.b6) — the Fedora/EPEL
# convention. Reversing to "0.<suffix>.<pkg_release>" (e.g. 0.b6.1) breaks
# rpmvercmp against the former because numeric segments outrank alphabetic
# ones, so "0.1.b5" would sort newer than "0.b6.1".
local rpm_release="${PKG_RELEASE}"
[[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${PKG_RELEASE}.${VER_SUFFIX}"
# Pre-releases use the modern rpm '~' convention (rpm >= 4.10): the suffix
# goes in Version (e.g. 3.2.0~b1), which rpmvercmp sorts *before* the final
# 3.2.0 — identical semantics to Debian's '~'. Release is just the package
# release number. This replaces the older "0.<release>.<suffix>" Release
# hack and keeps the RPM and DEB version strings symmetric.
local rpm_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}"
set -x
rpmbuild -bb \
--define "_topdir ${topdir}" \
--define "xrpld_version ${VER_BASE}" \
--define "xrpld_release ${rpm_release}" \
--define "xrpld_version ${rpm_version}" \
--define "xrpld_release ${PKG_RELEASE}" \
"${topdir}/SPECS/xrpld.spec"
}
@@ -181,13 +177,10 @@ build_deb() {
stage_common "${staging}"
cp -r "${DEBIAN_DIR}" "${staging}/debian"
# Debhelper auto-discovers these only from debian/.
cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service"
cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers"
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service"
cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer"
# Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0.
local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}"

View File

@@ -10,7 +10,6 @@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
override_dh_installsystemd:
dh_installsystemd --no-stop-on-upgrade xrpld.service
dh_installsystemd --name=update-xrpld --no-enable --no-start update-xrpld.service update-xrpld.timer
execute_before_dh_installtmpfiles:
dh_installsysusers
@@ -21,7 +20,6 @@ override_dh_install:
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
install -D -m 0755 update-xrpld debian/xrpld/usr/libexec/xrpld/update-xrpld
override_dh_dwz:
@:

View File

@@ -1,2 +1 @@
README.md
LICENSE.md

View File

@@ -35,8 +35,6 @@ install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{
# systemd units, sysusers, tmpfiles, preset
install -Dm0644 %{_sourcedir}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service
install -Dm0644 %{_sourcedir}/update-xrpld.service %{buildroot}%{_unitdir}/update-xrpld.service
install -Dm0644 %{_sourcedir}/update-xrpld.timer %{buildroot}%{_unitdir}/update-xrpld.timer
install -Dm0644 %{_sourcedir}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf
install -Dm0644 %{_sourcedir}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf
install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-xrpld.preset
@@ -44,9 +42,6 @@ install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-
# Logrotate config
install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
# Update helper
install -Dm0755 %{_sourcedir}/update-xrpld %{buildroot}%{_libexecdir}/%{name}/update-xrpld
# Docs
install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md
install -Dm0644 %{_sourcedir}/README.md %{buildroot}%{_docdir}/%{name}/README.md
@@ -61,10 +56,10 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
%post
systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
%systemd_post xrpld.service update-xrpld.timer
%systemd_post xrpld.service
%preun
%systemd_preun xrpld.service update-xrpld.timer
%systemd_preun xrpld.service
%postun
%systemd_postun_with_restart xrpld.service
@@ -74,7 +69,6 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
%doc %{_docdir}/%{name}/README.md
%dir %{_sysconfdir}/%{name}
%dir %{_libexecdir}/%{name}
%{_bindir}/%{name}
@@ -82,18 +76,13 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
%{_libexecdir}/%{name}/update-xrpld
%{_unitdir}/xrpld.service
%{_unitdir}/update-xrpld.service
%{_unitdir}/update-xrpld.timer
%{_presetdir}/50-xrpld.preset
%{_sysusersdir}/xrpld.conf
%{_tmpfilesdir}/xrpld.conf
%ghost %dir /var/lib/%{name}
%ghost %dir /var/log/%{name}
%ghost %dir /var/lib/xrpld
%ghost %dir /var/log/xrpld
# Legacy compatibility for pre-FHS package layouts.
# TODO: remove after rippled fully deprecated.

View File

@@ -1,4 +1,2 @@
# /usr/lib/systemd/system-preset/50-xrpld.preset
enable xrpld.service
# Don't enable automatic updates
disable update-xrpld.timer

View File

@@ -1,152 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Optional: also write logs to a legacy file in addition to journald.
# By default, this script logs to systemd/journald, viewable via:
# journalctl -t update-xrpld
#
# Uncomment the line below if you need a flat file for compatibility with
# external tooling, manual inspection, or environments where journald logs
# are not persisted or easily accessible.
#
# Note: This duplicates all output (stdout/stderr) to both journald and the file.
# It is generally not needed on modern systems and may cause log file growth
# if left enabled long-term.
#
# Requires /var/log/xrpld/ to exist and be writable by the service (root).
#
# exec > >(tee -a /var/log/xrpld/update.log) 2>&1
PATH=/usr/sbin:/usr/bin:/sbin:/bin
PKG_NAME=${PKG_NAME:-xrpld}
log() {
# If running under systemd/journald, let it handle timestamps.
if [[ -n "${JOURNAL_STREAM:-}" ]]; then
printf '%s\n' "$*"
else
printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
fi
}
require_root() {
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
log "RESULT: failed reason=not-root"
exit 1
fi
}
get_installed_version() {
if command -v dpkg-query >/dev/null 2>&1; then
dpkg-query -W -f='${Version}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
elif command -v rpm >/dev/null 2>&1; then
rpm -q --qf '%{VERSION}-%{RELEASE}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
else
printf 'unknown'
fi
}
trap 'log "RESULT: failed reason=script-error exit_code=$?"' ERR
apt_can_update() {
apt-get update -qq
apt-get -s --only-upgrade install "$PKG_NAME" 2>/dev/null | grep -q "^Inst ${PKG_NAME}\b"
}
apt_apply_update() {
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
-o Dpkg::Options::="--force-confdef" \
-o Dpkg::Options::="--force-confold" \
"$PKG_NAME"
}
get_rpm_pm() {
if command -v dnf >/dev/null 2>&1; then
printf 'dnf\n'
elif command -v yum >/dev/null 2>&1; then
printf 'yum\n'
else
return 1
fi
}
rpm_refresh_metadata() {
local pm=$1
if [[ "$pm" == "dnf" ]]; then
dnf makecache --refresh -q >/dev/null
else
yum clean expire-cache -q >/dev/null
fi
}
rpm_can_update() {
local pm=$1
rpm_refresh_metadata "$pm"
local rc=0
set +e
"$pm" check-update -q "$PKG_NAME" >/dev/null 2>&1
rc=$?
set -e
if [[ $rc -eq 100 ]]; then
return 0
elif [[ $rc -eq 0 ]]; then
return 1
else
log "$pm check-update failed with exit code ${rc}."
exit 1
fi
}
rpm_apply_update() {
local pm=$1
"$pm" update -y "$PKG_NAME"
}
restart_service() {
# Preserve the operator's prior service state: if xrpld was intentionally
# stopped before the update, don't bring it back up just because the
# auto-update timer fired.
if systemctl is-active --quiet "${PKG_NAME}.service"; then
systemctl restart "${PKG_NAME}.service"
log "${PKG_NAME} service restarted successfully."
else
log "${PKG_NAME} service was not running; skipping restart to preserve prior state."
fi
}
main() {
require_root
if command -v apt-get >/dev/null 2>&1; then
log "Checking for ${PKG_NAME} updates via apt"
if apt_can_update; then
log "Update available; installing."
apt_apply_update
restart_service
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
else
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
fi
return
fi
local rpm_pm=""
if rpm_pm="$(get_rpm_pm)"; then
log "Checking for ${PKG_NAME} updates via ${rpm_pm}"
if rpm_can_update "$rpm_pm"; then
log "Update available; installing"
rpm_apply_update "$rpm_pm"
restart_service
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
else
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
fi
return
fi
log "RESULT: failed reason=no-package-manager"
exit 1
}
main "$@"

View File

@@ -1,16 +0,0 @@
[Unit]
Description=Check for and install xrpld package updates
Documentation=man:systemd.service(5)
Wants=network-online.target
After=network-online.target
ConditionPathExists=/usr/libexec/xrpld/update-xrpld
ConditionPathExists=/usr/bin/xrpld
[Service]
Type=oneshot
ExecStart=/usr/bin/flock -n /run/lock/xrpld-update.lock /usr/libexec/xrpld/update-xrpld
StandardOutput=journal
StandardError=journal
SyslogIdentifier=update-xrpld
TimeoutStartSec=30min
PrivateTmp=true

View File

@@ -1,10 +0,0 @@
[Unit]
Description=Daily xrpld update check
[Timer]
OnCalendar=*-*-* 00:00:00
RandomizedDelaySec=4h
Persistent=true
[Install]
WantedBy=timers.target

View File

@@ -18,6 +18,11 @@ PrivateTmp=true
User=xrpld
Group=xrpld
LimitNOFILE=65536
SystemCallArchitectures=native
# Uncomment both lines to allow xrpld to bind to privileged ports (<1024)
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
#AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target

View File

@@ -1 +1 @@
halt_on_error=false
halt_on_error=true

View File

@@ -72,7 +72,7 @@ vptr:boost
# Google protobuf - intentional overflows in hash functions
undefined:protobuf
unsigned-integer-overflow:google/protobuf/stubs/stringpiece.h
unsigned-integer-overflow:protobuf
# gRPC intentional overflows in timer calculations
unsigned-integer-overflow:grpc
@@ -102,47 +102,103 @@ undefined:nudb
# Snappy compression library intentional overflows
unsigned-integer-overflow:snappy.cc
# Abseil intentional overflows
unsigned-integer-overflow:absl/strings/numbers.cc
unsigned-integer-overflow:absl/strings/internal/cord_rep_flat.h
unsigned-integer-overflow:absl/base/internal/low_level_alloc.cc
unsigned-integer-overflow:absl/hash/internal/hash.h
unsigned-integer-overflow:absl/container/internal/raw_hash_set.h
# Abseil intentional overflows in hashing, RNG and time arithmetic.
# Matched at library scope (like boost above): the wraparound is by design
# across many absl files (hash mixing, raw_hash_set probing, duration math,
# int128, uniform_int_distribution), so listing individual files just churns.
unsigned-integer-overflow:absl
# Standard library intentional overflows
unsigned-integer-overflow:basic_string.h
unsigned-integer-overflow:bits/align.h
unsigned-integer-overflow:bits/basic_string.tcc
unsigned-integer-overflow:bits/chrono.h
unsigned-integer-overflow:bits/random.h
unsigned-integer-overflow:bits/random.tcc
unsigned-integer-overflow:bits/stl_algobase.h
unsigned-integer-overflow:bits/string_view.tcc
unsigned-integer-overflow:bits/uniform_int_dist.h
unsigned-integer-overflow:string_view
unsigned-integer-overflow:__random/seed_seq.h
unsigned-integer-overflow:__charconv/traits.h
unsigned-integer-overflow:__chrono/duration.h
# libstdc++ <bit> (std::__bit_ceil etc.) negates an unsigned width; <bit> is a
# distinct header from the bits/ directory so it needs its own entry.
unsigned-integer-overflow:include/c++/*/bit
# =============================================================================
# Rippled code suppressions
# =============================================================================
# Signed integer negation (-value) in amount types.
# INT64_MIN cannot occur in practice due to domain invariants (mantissa ranges
# are well within int64_t bounds), but UBSan flags the pattern as potential
# signed overflow. Narrowed to operator- to avoid suppressing unrelated
# overflows anywhere in a stack trace containing these type names.
signed-integer-overflow:operator-*IOUAmount*
signed-integer-overflow:operator-*XRPAmount*
signed-integer-overflow:operator-*MPTAmount*
signed-integer-overflow:operator-*STAmount*
# These suppressions are keyed by SOURCE FILE, not function name. This UBSan
# build runs without symbol information, so the runtime only knows the
# file:line of each report, never the enclosing function — function-name
# patterns silently never match. Each entry below is therefore scoped to the
# file whose arithmetic is intentional; the comment names the specific
# construct.
# STAmount::operator+ signed addition — operands are bounded by total supply
# (~10^17 for XRP, ~10^18 for MPT) so overflow cannot occur in practice.
signed-integer-overflow:operator+*STAmount*
# STAmount amount-type arithmetic. Unary negation of the mantissa in xrp()/
# iou()/mpt()/canonicalize() and getInt64Value, plus bounded +/- on amounts:
# INT64_MIN cannot occur because canonicalize() keeps the mantissa well within
# int64_t, and operands are bounded by total supply (~10^17 XRP, ~10^18 MPT).
signed-integer-overflow:protocol/STAmount.cpp
# STAmount::getRate uses unsigned shift and addition
unsigned-integer-overflow:*STAmount*getRate*
# STAmount::serialize uses unsigned bitwise operations
unsigned-integer-overflow:*STAmount*serialize*
# nft::cipheredTaxon uses intentional uint32 wraparound (LCG permutation);
# the helper lives in the generated protocol header nft.h.
unsigned-integer-overflow:protocol/nft.h
# nft::cipheredTaxon uses intentional uint32 wraparound (LCG permutation)
unsigned-integer-overflow:cipheredTaxon
# STPathElement::getHash multiplies/adds accumulators (non-secure, speed-first).
unsigned-integer-overflow:protocol/STPathSet.cpp
# beast XorShiftEngine PRNG and murmurhash3 mixing wrap by design.
unsigned-integer-overflow:beast/xor_shift_engine.h
# Number::normalizeToRange multiplies the mantissa by powers of ten; the result
# is intentionally allowed to wrap while searching for the in-range value.
unsigned-integer-overflow:basics/Number.h
# Counter / sequence arithmetic with intentional unsigned wraparound, each
# guarded by an explicit overflow or domain check at the call site:
# base_uint operator++/-- wrap by definition;
# ApplyView::insertPage ++page is asserted to wrap to 0 (page exhaustion);
# confineOwnerCount documents "overflow is well defined on unsigned";
# NFTokenMint checks tokenSeq + 1u == 0u; AmendmentTable does (seq - 1) / 256.
unsigned-integer-overflow:basics/base_uint.h
unsigned-integer-overflow:ledger/ApplyView.cpp
unsigned-integer-overflow:ledger/helpers/AccountRootHelpers.cpp
unsigned-integer-overflow:tx/transactors/nft/NFTokenMint.cpp
unsigned-integer-overflow:app/misc/detail/AmendmentTable.cpp
# Sentinel / bounded subtractions that wrap by design (loop counters, reverse
# iteration, "not found" sentinels, balance math bounded by issuance invariants,
# base58/base64 codec index math, hash-router and role bit math).
unsigned-integer-overflow:shamap/SHAMap.cpp
unsigned-integer-overflow:protocol/Permissions.cpp
unsigned-integer-overflow:protocol/tokens.cpp
unsigned-integer-overflow:basics/base64.cpp
unsigned-integer-overflow:json/json_value.cpp
unsigned-integer-overflow:app/misc/NetworkOPs.cpp
unsigned-integer-overflow:rpc/detail/Role.cpp
unsigned-integer-overflow:tx/transactors/oracle/OracleSet.cpp
unsigned-integer-overflow:ledger/helpers/MPTokenHelpers.cpp
unsigned-integer-overflow:crypto/RFC1751.cpp
unsigned-integer-overflow:tx/paths/detail/StrandFlow.h
unsigned-integer-overflow:protocol/STObject.h
# GetAggregatePrice negates an unsigned trim count to step a reverse iterator;
# trimCount is bounded by the price set size.
unsigned-integer-overflow:rpc/handlers/orderbook/GetAggregatePrice.cpp
# Test-only intentional overflow/underflow in fixture and unit-test arithmetic.
unsigned-integer-overflow:tests/libxrpl/basics/RangeSet.cpp
unsigned-integer-overflow:test/app/Batch_test.cpp
unsigned-integer-overflow:test/app/Invariants_test.cpp
unsigned-integer-overflow:test/app/Loan_test.cpp
unsigned-integer-overflow:test/app/NFToken_test.cpp
unsigned-integer-overflow:test/app/OfferMPT_test.cpp
unsigned-integer-overflow:test/app/Offer_test.cpp
unsigned-integer-overflow:test/app/Path_test.cpp
unsigned-integer-overflow:test/jtx/impl/acctdelete.cpp
unsigned-integer-overflow:test/ledger/SkipList_test.cpp
unsigned-integer-overflow:test/rpc/Subscribe_test.cpp
signed-integer-overflow:test/basics/XRPAmount_test.cpp

View File

@@ -1,55 +0,0 @@
#include <xrpl/ledger/BookListeners.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/server/InfoSub.h>
#include <cstdint>
#include <mutex>
namespace xrpl {
void
BookListeners::addSubscriber(InfoSub::ref sub)
{
std::scoped_lock const sl(lock_);
listeners_[sub->getSeq()] = sub;
}
void
BookListeners::removeSubscriber(std::uint64_t seq)
{
std::scoped_lock const sl(lock_);
listeners_.erase(seq);
}
void
BookListeners::publish(MultiApiJson const& jvObj, hash_set<std::uint64_t>& havePublished)
{
std::scoped_lock const sl(lock_);
auto it = listeners_.cbegin();
while (it != listeners_.cend())
{
InfoSub::pointer p = it->second.lock();
if (p)
{
// Only publish jvObj if this is the first occurrence
if (havePublished.emplace(p->getSeq()).second)
{
jvObj.visit(
p->getApiVersion(), //
[&](json::Value const& jv) { p->send(jv, true); });
}
++it;
}
else
{
it = listeners_.erase(it);
}
}
}
} // namespace xrpl

View File

@@ -14,6 +14,7 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -443,7 +444,7 @@ doWithdraw(
// Create trust line or MPToken for the receiving account
if (dstAcct == senderAcct)
{
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
if (auto const ter = addEmptyHolding(view, tx, senderAcct, priorBalance, amount.asset(), j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
@@ -469,9 +470,13 @@ doWithdraw(
// LCOV_EXCL_STOP
}
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Move the funds directly from the broker's pseudo-account to the
// dstAcct
return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
return accountSend(view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes);
}
TER

View File

@@ -8,6 +8,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -15,6 +16,7 @@
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/digest.h>
@@ -83,6 +85,109 @@ confineOwnerCount(
return adjusted;
}
static std::uint32_t
ownerCountHlp(
ReadView const& view,
SLE::const_ref sle,
std::int32_t adjustment,
bool reportConfine,
beast::Journal j)
{
AccountID const id = sle->getAccountID(sfAccount);
std::uint32_t const savedCount = sle->at(sfOwnerCount);
std::uint32_t const hookedCount = view.ownerCountHook(id, savedCount);
std::uint32_t const sponsoredCount = sle->at(sfSponsoredOwnerCount);
std::uint32_t const sponsoringCount = sle->at(sfSponsoringOwnerCount);
if (hookedCount < sponsoredCount)
{
Throw<std::logic_error>(
"xrpl::ownerCountHlp : OwnerCount must be greater than or equal to "
"SponsoredOwnerCount");
}
std::int64_t deltaCount =
static_cast<std::int64_t>(adjustment) - sponsoredCount + sponsoringCount;
if (deltaCount > std::numeric_limits<std::int32_t>::max())
{
deltaCount = std::numeric_limits<std::int32_t>::max();
JLOG(j.fatal()) << "Account " << id << " delta count exceeds max, "
<< "adjustment: " << adjustment << ", sponsoredCount: " << sponsoredCount
<< ", sponsoringOwnerCount: " << sponsoringCount;
}
else if (deltaCount < std::numeric_limits<std::int32_t>::min())
{
deltaCount = std::numeric_limits<std::int32_t>::min();
JLOG(j.fatal()) << "Account " << id << " delta count exceeds min, "
<< "adjustment: " << adjustment << ", sponsoredCount: " << sponsoredCount
<< ", sponsoringCount: " << sponsoringCount;
}
std::uint32_t const confinedCount = reportConfine
? confineOwnerCount(hookedCount, deltaCount, id, j)
: confineOwnerCount(hookedCount, deltaCount);
return confinedCount;
}
static std::uint32_t
reserveCountHlp(SLE::const_ref sle, std::int32_t adjustment, beast::Journal j)
{
bool const isSponsored = sle->isFieldPresent(sfSponsor);
std::uint32_t const sponsoringCount = sle->getFieldU32(sfSponsoringAccountCount);
std::uint32_t const reserveCount = (isSponsored ? 0 : 1) + sponsoringCount;
std::uint32_t adjusted{reserveCount + adjustment};
if (adjustment > 0)
{
// Overflow is well defined on unsigned
if (adjusted < reserveCount)
{
JLOG(j.fatal()) << "Reserve count exceeds max!";
adjusted = std::numeric_limits<std::uint32_t>::max();
}
}
else
{
// Underflow is well defined on unsigned
if (adjusted > reserveCount)
{
JLOG(j.fatal()) << "Reserve count set below 0!";
adjusted = 0;
}
}
return adjusted;
}
static inline XRPAmount
baseReserveHlp(ReadView const& view, std::uint32_t ownerCount, std::uint32_t reserveCount)
{
auto const& fees = view.fees();
return (fees.reserve * reserveCount) + (fees.increment * ownerCount);
}
static XRPAmount
reserveHlp(
ReadView const& view,
SLE::const_ref sle,
std::uint32_t ownerCount,
std::uint32_t reserveCount)
{
// Pseudo-accounts have no reserve requirement
if (isPseudoAccount(sle))
return XRPAmount(0);
auto const reserve = baseReserveHlp(view, ownerCount, reserveCount);
return reserve;
}
std::uint32_t
ownerCount(ReadView const& view, SLE::const_ref sle, beast::Journal j, std::int32_t adjustment)
{
return ownerCountHlp(view, sle, adjustment, true, j);
}
XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
{
@@ -90,13 +195,9 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
if (sle == nullptr)
return beast::kZero;
// Return balance minus reserve
std::uint32_t const ownerCount =
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
// Pseudo-accounts have no reserve requirement
auto const reserve =
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
std::uint32_t const ownerCount = ownerCountHlp(view, sle, ownerCountAdj, false, j);
std::uint32_t const reserveCount = reserveCountHlp(sle, 0, j);
auto const reserve = reserveHlp(view, sle, ownerCount, reserveCount);
auto const fullBalance = sle->getFieldAmount(sfBalance);
@@ -124,20 +225,158 @@ transferRate(ReadView const& view, AccountID const& issuer)
return kParityRate;
}
void
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j)
static void
adjustOwnerCountHlp(
ApplyView& view,
SLE::ref sle,
SF_UINT32 const& sfield,
AccountID const& accID,
std::int32_t adjustment,
beast::Journal j,
bool callHook = true)
{
if (!sle)
return;
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
AccountID const id = (*sle)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
view.adjustOwnerCountHook(id, current, adjusted);
sle->at(sfOwnerCount) = adjusted;
std::uint32_t const current = sle->at(sfield);
std::uint32_t const adjusted = confineOwnerCount(current, adjustment, accID, j);
if (callHook)
view.adjustOwnerCountHook(accID, current, adjusted);
sle->at(sfield) = adjusted;
view.update(sle);
}
void
adjustOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::int32_t adjustment,
beast::Journal j)
{
if (!accountSle)
Throw<std::runtime_error>("xrpl::adjustOwnerCount : valid account sle");
auto const sleType = accountSle->getType();
bool const validType = sponsorSle ? sleType == ltACCOUNT_ROOT
: sleType == ltLOAN_BROKER || sleType == ltACCOUNT_ROOT;
if (!validType)
Throw<std::logic_error>("xrpl::adjustOwnerCount : valid account sle type");
XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCount : nonzero adjustment input");
if (adjustment == 0)
return;
auto const accountID = accountSle->getAccountID(sfAccount);
if (sponsorSle)
{
if (sponsorSle->getType() != ltACCOUNT_ROOT)
Throw<std::logic_error>("xrpl::adjustOwnerCount : valid sponsor sle type");
auto const sponsorID = sponsorSle->getAccountID(sfAccount);
adjustOwnerCountHlp(view, accountSle, sfSponsoredOwnerCount, accountID, adjustment, j);
adjustOwnerCountHlp(view, sponsorSle, sfSponsoringOwnerCount, sponsorID, adjustment, j);
auto sponsorObjSle = view.peek(keylet::sponsor(sponsorID, accountID));
if (sponsorObjSle && adjustment > 0)
{
// update the pre-funded ReserveCount on Sponsorship ledger object
// Reserve count moves opposite to adjustment: +adjustment => consume reserve (-),
adjustOwnerCountHlp(
view, sponsorObjSle, sfReserveCount, sponsorID, -adjustment, j, false);
}
}
adjustOwnerCountHlp(view, accountSle, sfOwnerCount, accountID, adjustment, j);
}
void
adjustOwnerCountObj(
ApplyView& view,
SLE::ref accountSle,
SLE::ref objectSle,
std::int32_t amount,
beast::Journal j)
{
if (!objectSle)
Throw<std::runtime_error>("xrpl::adjustOwnerCount : valid object sle");
if (objectSle->getType() == ltACCOUNT_ROOT)
Throw<std::logic_error>("xrpl::adjustOwnerCount : valid object sle type");
SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle);
adjustOwnerCount(view, accountSle, sponsorSle, amount, j);
}
XRPAmount
accountReserve(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj,
std::int32_t reserveCountAdj)
{
if (!sle)
Throw<std::runtime_error>("xrpl::accountReserve : valid sle");
if (sle->getType() != ltACCOUNT_ROOT)
Throw<std::logic_error>("xrpl::accountReserve : valid sle type");
std::uint32_t const ownerCount = ownerCountHlp(view, sle, ownerCountAdj, true, j);
std::uint32_t const reserveCount = reserveCountHlp(sle, reserveCountAdj, j);
return reserveHlp(view, sle, ownerCount, reserveCount);
}
XRPAmount
baseAccountReserve(ReadView const& view, std::int32_t ownerCount)
{
auto const reserve = baseReserveHlp(view, ownerCount, 1);
return reserve;
}
TER
checkInsufficientReserve(
ReadView const& view,
STTx const& tx,
SLE::const_ref accSle,
STAmount const& accBalance,
SLE::const_ref sponsorSle,
std::int32_t ownerCountDelta,
std::int32_t reserveCountDelta,
beast::Journal j)
{
if (sponsorSle)
{
auto const isCoSigning = isSponsorReserveCoSigning(tx);
auto const sle = view.read(
keylet::sponsor(sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount)));
// prefunded sponsor should have a sponsorship entry
if (!isCoSigning && !sle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (sle)
{
auto const ownerCountAllowed = sle->getFieldU32(sfReserveCount);
if (ownerCountAllowed < ownerCountDelta)
return tecINSUFFICIENT_RESERVE;
}
auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance);
STAmount const sponsorReserve =
accountReserve(view, sponsorSle, j, ownerCountDelta, reserveCountDelta);
if (sponsorBalance < sponsorReserve)
return tecINSUFFICIENT_RESERVE;
}
else
{
STAmount const reserve =
accountReserve(view, accSle, j, ownerCountDelta, reserveCountDelta);
if (accBalance < reserve)
return tecINSUFFICIENT_RESERVE;
}
return tesSUCCESS;
}
// ----------------------------------------------------
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
{
@@ -188,7 +427,7 @@ getPseudoAccountFields()
}
[[nodiscard]] bool
isPseudoAccount(SLE::const_pointer sleAcct, std::set<SField const*> const& pseudoFieldFilter)
isPseudoAccount(SLE::const_ref sleAcct, std::set<SField const*> const& pseudoFieldFilter)
{
auto const& fields = getPseudoAccountFields();

View File

@@ -97,7 +97,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j)
}
if (isOwner)
adjustOwnerCount(view, sleAccount, -1, j);
adjustOwnerCountObj(view, sleAccount, sleCredential, -1, j);
return tesSUCCESS;
};

View File

@@ -3,7 +3,6 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
@@ -11,6 +10,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -23,6 +23,7 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UintTypes.h>
@@ -126,6 +127,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
@@ -142,12 +144,13 @@ addEmptyHolding(
if (accountID == mptIssue.getIssuer())
return tesSUCCESS;
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
return authorizeMPToken(view, tx, priorBalance, mptID, accountID, journal);
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
STTx const& tx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -180,7 +183,7 @@ authorizeMPToken(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleAcct, -1, journal);
adjustOwnerCountObj(view, sleAcct, sleMpt, -1, journal);
view.erase(sleMpt);
return tesSUCCESS;
@@ -190,18 +193,27 @@ authorizeMPToken(
// - add the new mptokenKey to the owner directory
// - create the MPToken object for the holder
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
auto const isSponsoredAndPreFunded = *sponsorSle && !isSponsorReserveCoSigning(tx);
// The reserve that is required to create the MPToken. Note
// that although the reserve increases with every item
// an account owns, in the case of MPTokens we only
// *enforce* a reserve if the user owns more than two
// items. This is similar to the reserve requirements of trust lines.
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
XRPAmount const reserveCreate(
(uOwnerCount < 2) ? XRPAmount(beast::kZero)
: view.fees().accountReserve(uOwnerCount + 1));
if (priorBalance < reserveCreate)
return tecINSUFFICIENT_RESERVE;
// If PreFunded Sponsor, it must be checked whether sufficient
// ReserveCount exists.
if (ownerCount(view, *sponsorSle ? *sponsorSle : sleAcct, journal) >= 2 ||
isSponsoredAndPreFunded)
{
if (auto const ret = checkInsufficientReserve(
view, tx, sleAcct, priorBalance, *sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
return ret;
}
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
@@ -225,7 +237,8 @@ authorizeMPToken(
view.insert(mptoken);
// Update owner count.
adjustOwnerCount(view, sleAcct, 1, journal);
adjustOwnerCount(view, sleAcct, *sponsorSle, 1, journal);
addSponsorToLedgerEntry(mptoken, *sponsorSle);
return tesSUCCESS;
}
@@ -270,6 +283,7 @@ authorizeMPToken(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
@@ -292,6 +306,7 @@ removeEmptyHolding(
return authorizeMPToken(
view,
tx,
{}, // priorBalance
mptID,
accountID,
@@ -401,6 +416,7 @@ requireAuth(
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
STTx const& tx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
@@ -482,6 +498,7 @@ enforceMPTokenAuthorization(
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
view,
tx,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
@@ -896,6 +913,7 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::ref sponsorSle,
std::uint32_t const flags)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
@@ -912,6 +930,9 @@ createMPToken(
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
if (sponsorSle)
addSponsorToLedgerEntry(mptoken, sponsorSle);
view.insert(mptoken);
return tesSUCCESS;
@@ -922,6 +943,7 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
beast::Journal j)
{
if (mptIssue.getIssuer() == holder)
@@ -931,7 +953,7 @@ checkCreateMPT(
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
if (!view.exists(mptokenID))
{
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, 0);
!isTesSuccess(err))
{
return err;
@@ -941,7 +963,8 @@ checkCreateMPT(
{
return tecINTERNAL;
}
adjustOwnerCount(view, sleAcct, 1, j);
adjustOwnerCount(view, sleAcct, sponsorSle, 1, j);
}
return tesSUCCESS;
}

View File

@@ -4,12 +4,14 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -21,6 +23,7 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
@@ -32,7 +35,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <expected>
#include <iterator>
#include <memory>
#include <optional>
@@ -67,12 +70,13 @@ locatePage(ApplyView& view, AccountID const& owner, uint256 const& id)
Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key)));
}
static SLE::pointer
static std::expected<SLE::pointer, TER>
getPageForToken(
ApplyView& view,
STTx const& tx,
AccountID const& owner,
uint256 const& id,
std::function<void(ApplyView&, AccountID const&)> const& createCallback)
SLE::ref sponsorSle,
uint256 const& id)
{
auto const base = keylet::nftpageMin(owner);
auto const first = keylet::nftpage(base, id);
@@ -84,6 +88,21 @@ getPageForToken(
auto cp =
view.peek(Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key)));
auto const onNewPage = [&](SLE::ref newPage) -> TER {
if (isReserveSponsored(tx))
{
auto const ownerSle = view.read(keylet::account(owner));
auto const ownerBalance = ownerSle->getFieldAmount(sfBalance);
if (auto const ret =
checkInsufficientReserve(view, tx, ownerSle, ownerBalance, sponsorSle, 1);
!isTesSuccess(ret))
return ret;
}
adjustOwnerCount(view, view.peek(keylet::account(owner)), sponsorSle, 1);
addSponsorToLedgerEntry(newPage, sponsorSle);
return tesSUCCESS;
};
// A suitable page doesn't exist; we'll have to create one.
if (!cp)
{
@@ -91,7 +110,9 @@ getPageForToken(
cp = std::make_shared<SLE>(last);
cp->setFieldArray(sfNFTokens, arr);
view.insert(cp);
createCallback(view, owner);
if (auto const ret = onNewPage(cp); !isTesSuccess(ret))
return std::unexpected(ret);
return cp;
}
@@ -204,7 +225,8 @@ getPageForToken(
cp->setFieldH256(sfPreviousPageMin, np->key());
view.update(cp);
createCallback(view, owner);
if (auto const ret = onNewPage(np); !isTesSuccess(ret))
return std::unexpected(ret);
return (first.key < np->key()) ? np : cp;
}
@@ -260,37 +282,33 @@ changeTokenURI(
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft)
insertToken(ApplyView& view, STTx const& tx, AccountID owner, SLE::ref sponsorSle, STObject&& nft)
{
XRPL_ASSERT(nft.isFieldPresent(sfNFTokenID), "xrpl::nft::insertToken : has NFT token");
// First, we need to locate the page the NFT belongs to, creating it
// if necessary. This operation may fail if it is impossible to insert
// the NFT.
SLE::pointer const page =
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
1,
beast::Journal{beast::Journal::getNullSink()});
});
auto const page = getPageForToken(view, tx, owner, sponsorSle, nft[sfNFTokenID]);
if (!page)
if (!page.has_value())
return page.error();
if (!(*page))
return tecNO_SUITABLE_NFTOKEN_PAGE;
{
auto arr = page->getFieldArray(sfNFTokens);
auto arr = (*page)->getFieldArray(sfNFTokens);
arr.pushBack(std::move(nft));
arr.sort([](STObject const& o1, STObject const& o2) {
return compareTokens(o1.getFieldH256(sfNFTokenID), o2.getFieldH256(sfNFTokenID));
});
page->setFieldArray(sfNFTokens, arr);
(*page)->setFieldArray(sfNFTokens, arr);
}
view.update(page);
view.update((*page));
return tesSUCCESS;
}
@@ -411,22 +429,11 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
curr->setFieldArray(sfNFTokens, arr);
view.update(curr);
int cnt = 0;
if (prev && mergePages(view, prev, curr))
cnt--;
adjustOwnerCountObj(view, owner, prev, -1);
if (next && mergePages(view, curr, next))
cnt--;
if (cnt != 0)
{
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
cnt,
beast::Journal{beast::Journal::getNullSink()});
}
adjustOwnerCountObj(view, owner, curr, -1);
return tesSUCCESS;
}
@@ -460,11 +467,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
curr->makeFieldAbsent(sfPreviousPageMin);
}
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1,
beast::Journal{beast::Journal::getNullSink()});
adjustOwnerCountObj(view, owner, prev, -1);
view.update(curr);
view.erase(prev);
@@ -500,9 +503,9 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
view.update(next);
}
view.erase(curr);
adjustOwnerCountObj(view, owner, curr, -1);
int cnt = 1;
view.erase(curr);
// Since we're here, try to consolidate the previous and current pages
// of the page we removed (if any) into one. mergePages() _should_
@@ -517,13 +520,9 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
view,
view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())),
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
cnt++;
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1 * cnt,
beast::Journal{beast::Journal::getNullSink()});
{
adjustOwnerCountObj(view, owner, prev, -1);
}
return tesSUCCESS;
}
@@ -639,8 +638,7 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer)
false))
return false;
adjustOwnerCount(
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
adjustOwnerCountObj(view, owner, offer, -1);
view.erase(offer);
return true;
@@ -745,7 +743,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
{
Throw<std::runtime_error>(
"NFTokenPage directory for " + to_string(owner) +
" cannot be repaired. Unexpected link problem.");
" cannot be repaired. std::unexpected link problem.");
}
newPrev->at(sfNextPageMin) = nextPage->key();
view.update(newPrev);
@@ -919,6 +917,7 @@ tokenOfferCreatePreclaim(
TER
tokenOfferCreateApply(
ApplyView& view,
STTx const& tx,
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,
@@ -930,9 +929,14 @@ tokenOfferCreateApply(
std::uint32_t txFlags)
{
Keylet const acctKeylet = keylet::account(acctID);
if (auto const acct = view.read(acctKeylet);
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
return tecINSUFFICIENT_RESERVE;
auto const acct = view.read(acctKeylet);
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret =
checkInsufficientReserve(view, tx, acct, priorBalance, *sponsorSle, 1, 0, j);
!isTesSuccess(ret))
return ret;
auto const offerID = keylet::nftoffer(acctID, seqProxy.value());
@@ -979,11 +983,13 @@ tokenOfferCreateApply(
if (dest)
(*offer)[sfDestination] = *dest;
addSponsorToLedgerEntry(offer, *sponsorSle);
view.insert(offer);
}
// Update owner count.
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
adjustOwnerCount(view, view.peek(acctKeylet), *sponsorSle, 1, j);
return tesSUCCESS;
}

View File

@@ -55,7 +55,7 @@ offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j)
}
}
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
adjustOwnerCountObj(view, owner, sle, -1, j);
view.erase(sle);

View File

@@ -5,13 +5,20 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <optional>
namespace xrpl {
TER
@@ -51,7 +58,7 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
adjustOwnerCount(view, sle, -1, j);
adjustOwnerCountObj(view, sle, slep, -1, j);
view.update(sle);
// Remove PayChan from ledger
@@ -59,4 +66,28 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
return tesSUCCESS;
}
uint32_t
saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs)
{
if (rules.enabled(fixCleanup3_2_0))
{
static constexpr auto kUint32Max =
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max());
uint64_t const saturatedResult = std::min(uint64_t{lhs} + rhs, kUint32Max);
return static_cast<uint32_t>(saturatedResult);
}
return lhs + rhs;
}
bool
isChannelExpired(ApplyView const& view, std::optional<uint32_t> timeField)
{
if (!timeField)
return false;
if (view.rules().enabled(fixCleanup3_2_0))
return after(view.header().parentCloseTime, *timeField);
return view.header().parentCloseTime.time_since_epoch().count() >= *timeField;
}
} // namespace xrpl

View File

@@ -9,6 +9,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/AmountConversions.h>
@@ -21,6 +22,7 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -29,6 +31,7 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
namespace xrpl {
@@ -195,6 +198,7 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
SLE::ref sponsorSle,
beast::Journal j)
{
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
@@ -281,7 +285,9 @@ trustCreate(
}
sleRippleState->setFieldU32(sfFlags, uFlags);
adjustOwnerCount(view, sleAccount, 1, j);
adjustOwnerCount(view, sleAccount, sponsorSle, 1, j);
addSponsorToLedgerEntry(sleRippleState, sponsorSle, bSetHigh ? sfHighSponsor : sfLowSponsor);
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
@@ -317,6 +323,9 @@ trustDelete(
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor);
removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor);
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
view.erase(sleRippleState);
@@ -369,11 +378,15 @@ updateTrustLine(
{
// VFALCO Where is the line being deleted?
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, sle, -1, j);
auto const currentSponsor =
getLedgerEntryReserveSponsor(view, state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
adjustOwnerCount(view, sle, currentSponsor, -1, j);
// Clear reserve flag.
state->clearFlag(senderReserveFlag);
removeSponsorFromLedgerEntry(state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
// Balance is zero, receiver reserve is clear.
if (!after && !state->isFlag(receiverReserveFlag))
return true;
@@ -472,6 +485,7 @@ issueIOU(
limit,
0,
0,
{},
j);
}
@@ -621,6 +635,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
@@ -649,9 +664,19 @@ addEmptyHolding(
if (view.read(index))
return tecDUPLICATE;
SLE::pointer sponsorSle;
if (!isPseudoAccount(sleDst))
{
auto sle = getTxReserveSponsor(view, tx);
if (!sle)
return sle.error(); // LCOV_EXCL_LINE
sponsorSle = std::move(*sle);
}
// Can the account cover the trust line reserve ?
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
if (auto const ret =
checkInsufficientReserve(view, tx, sleDst, priorBalance, sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
return tecNO_LINE_INSUF_RESERVE;
return trustCreate(
@@ -669,6 +694,7 @@ addEmptyHolding(
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
sponsorSle,
journal);
}
@@ -710,11 +736,14 @@ removeEmptyHolding(
if (!sleLowAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleLowAccount, -1, journal);
auto const currentLowSponsor = getLedgerEntryReserveSponsor(view, line, sfLowSponsor);
adjustOwnerCount(view, sleLowAccount, currentLowSponsor, -1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfLowReserve);
removeSponsorFromLedgerEntry(line, sfLowSponsor);
}
if (line->isFlag(lsfHighReserve))
@@ -724,11 +753,14 @@ removeEmptyHolding(
if (!sleHighAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleHighAccount, -1, journal);
auto const currentHighSponsor = getLedgerEntryReserveSponsor(view, line, sfHighSponsor);
adjustOwnerCount(view, sleHighAccount, currentHighSponsor, -1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfHighReserve);
removeSponsorFromLedgerEntry(line, sfHighSponsor);
}
return trustDelete(
@@ -768,6 +800,9 @@ deleteAMMTrustLine(
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
return terNO_AMM;
auto const sponsorSle =
getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor);
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
{
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
@@ -778,7 +813,7 @@ deleteAMMTrustLine(
if (!sleState->isFlag(uFlags))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, -1, j);
return tesSUCCESS;
}

View File

@@ -6,9 +6,11 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Concepts.h>
@@ -22,6 +24,7 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -35,12 +38,6 @@
namespace xrpl {
// Forward declaration for function that remains in View.h/cpp
bool
isLPTokenFrozen(
ReadView const& view,
AccountID const& account,
Asset const& asset,
Asset const& asset2);
//------------------------------------------------------------------------------
//
@@ -478,6 +475,7 @@ canAddHolding(ReadView const& view, Asset const& asset)
TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -485,7 +483,7 @@ addEmptyHolding(
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
return addEmptyHolding(view, tx, accountID, priorBalance, issue, journal);
},
asset.value());
}
@@ -493,13 +491,21 @@ addEmptyHolding(
TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return removeEmptyHolding(view, accountID, issue, journal);
if constexpr (std::is_same_v<TIss, Issue>)
{
return removeEmptyHolding(view, accountID, issue, journal);
}
else
{
return removeEmptyHolding(view, tx, accountID, issue, journal);
}
},
asset.value());
}
@@ -553,6 +559,7 @@ directSendNoFeeIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
bool bCheckIssuer,
SLE::ref sponsorSle,
beast::Journal j)
{
AccountID const& issuer = saAmount.getIssuer();
@@ -623,7 +630,12 @@ directSendNoFeeIOU(
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
auto const currentSponsor = getLedgerEntryReserveSponsor(
view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, -1, j);
removeSponsorFromLedgerEntry(
sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
// Clear reserve flag.
sleRippleState->clearFlag(senderReserveFlag);
@@ -686,6 +698,7 @@ directSendNoFeeIOU(
saReceiverLimit,
0,
0,
sponsorSle,
j);
}
@@ -700,6 +713,7 @@ directSendNoLimitIOU(
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
auto const& issuer = saAmount.getIssuer();
@@ -712,7 +726,8 @@ directSendNoLimitIOU(
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j);
auto const ter =
directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j);
if (!isTesSuccess(ter))
return ter;
saActual = saAmount;
@@ -730,10 +745,12 @@ directSendNoLimitIOU(
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
<< " cost=" << saActual.getFullText();
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j);
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j);
if (tesSUCCESS == terResult)
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j);
{
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j);
}
return terResult;
}
@@ -749,6 +766,7 @@ directSendNoLimitMultiIOU(
MultiplePaymentDestinations const& receivers,
STAmount& actual,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
auto const& issuer = issue.getIssuer();
@@ -776,7 +794,8 @@ directSendNoLimitMultiIOU(
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j))
if (auto const ter =
directSendNoFeeIOU(view, senderID, receiverID, amount, false, sponsorSle, j))
return ter;
actual += amount;
// Do not add amount to takeFromSender, because directSendNoFeeIOU took
@@ -799,14 +818,15 @@ directSendNoLimitMultiIOU(
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
<< " cost=" << actual.getFullText();
if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j))
if (TER const terResult =
directSendNoFeeIOU(view, issuer, receiverID, amount, true, sponsorSle, j))
return terResult;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult =
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j))
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, sponsorSle, j))
return terResult;
}
@@ -820,6 +840,7 @@ accountSendIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
if (view.rules().enabled(fixAMMv1_1))
@@ -851,7 +872,8 @@ accountSendIOU(
JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
<< to_string(uReceiverID) << " : " << saAmount.getFullText();
return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
return directSendNoLimitIOU(
view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee);
}
/* XRP send which does not check reserve and can do pure adjustment.
@@ -937,6 +959,7 @@ accountSendMultiIOU(
Issue const& issue,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT_PARTS(
@@ -948,7 +971,8 @@ accountSendMultiIOU(
JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending "
<< receivers.size() << " IOUs";
return directSendNoLimitMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee);
return directSendNoLimitMultiIOU(
view, senderID, issue, receivers, actual, j, sponsorSle, waiveFee);
}
/* XRP send which does not check reserve and can do pure adjustment.
@@ -1380,7 +1404,7 @@ directSendNoFee(
{
return saAmount.asset().visit(
[&](Issue const&) {
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j);
},
[&](MPTIssue const&) {
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
@@ -1395,12 +1419,13 @@ accountSend(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee,
AllowMPTOverflow allowOverflow)
{
return saAmount.asset().visit(
[&](Issue const&) {
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee);
},
[&](MPTIssue const&) {
return accountSendMPT(
@@ -1415,13 +1440,14 @@ accountSendMulti(
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT_PARTS(
receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
return asset.visit(
[&](Issue const& issue) {
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
return accountSendMultiIOU(view, senderID, issue, receivers, j, sponsorSle, waiveFee);
},
[&](MPTIssue const& issue) {
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);

View File

@@ -45,8 +45,10 @@ ManagerImp::missingBackend()
// the Factory classes is an undefined behaviour.
void
registerNuDBFactory(Manager& manager);
#if XRPL_ROCKSDB_AVAILABLE
void
registerRocksDBFactory(Manager& manager);
#endif
void
registerNullFactory(Manager& manager);
void
@@ -55,7 +57,9 @@ registerMemoryFactory(Manager& manager);
ManagerImp::ManagerImp()
{
registerNuDBFactory(*this);
#if XRPL_ROCKSDB_AVAILABLE
registerRocksDBFactory(*this);
#endif
registerNullFactory(*this);
registerMemoryFactory(*this);
}

View File

@@ -1,13 +1,23 @@
#if XRPL_ROCKSDB_AVAILABLE
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/config/Constants.h>
#include <xrpl/nodestore/Backend.h>
#include <xrpl/nodestore/Factory.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/Types.h>
#include <xrpl/nodestore/detail/BatchWriter.h>
#include <xrpl/nodestore/detail/DecodedBlob.h>
#include <xrpl/nodestore/detail/EncodedBlob.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
@@ -25,26 +35,14 @@
#include <rocksdb/table.h>
#include <rocksdb/write_batch.h>
#include <atomic>
#include <bit>
#include <cstddef>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#if XRPL_ROCKSDB_AVAILABLE
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/nodestore/Factory.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/detail/BatchWriter.h>
#include <xrpl/nodestore/detail/DecodedBlob.h>
#include <xrpl/nodestore/detail/EncodedBlob.h>
#include <atomic>
#include <memory>
namespace xrpl::NodeStore {
class RocksDBEnv : public rocksdb::EnvWrapper

View File

@@ -23,7 +23,7 @@ namespace {
//------------------------------------------------------------------------------
// clang-format off
// NOLINTNEXTLINE(readability-identifier-naming)
char const* const versionString = "3.2.0-rc3"
char const* const versionString = "3.3.0-b0"
// clang-format on
;

View File

@@ -84,6 +84,7 @@ enum class LedgerNameSpace : std::uint16_t {
Vault = 'V',
LoanBroker = 'l', // lower-case L
Loan = 'L',
Sponsorship = '>',
// No longer used or supported. Left here to reserve the space to avoid accidental reuse.
Contract [[deprecated]] = 'c',
@@ -318,6 +319,12 @@ signers(AccountID const& account) noexcept
return signers(account, 0);
}
Keylet
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept
{
return {ltSPONSORSHIP, indexHash(LedgerNameSpace::Sponsorship, sponsor, sponsee)};
}
Keylet
check(AccountID const& id, std::uint32_t seq) noexcept
{

View File

@@ -160,6 +160,14 @@ InnerObjectFormats::InnerObjectFormats()
{sfTxnSignature, SoeOptional},
{sfSigners, SoeOptional},
});
add(sfSponsorSignature.jsonName.cStr(),
sfSponsorSignature.getCode(),
{
{sfSigningPubKey, SoeOptional},
{sfTxnSignature, SoeOptional},
{sfSigners, SoeOptional},
});
}
InnerObjectFormats const&

View File

@@ -15,6 +15,7 @@ LedgerFormats::getCommonFields()
{sfLedgerIndex, SoeOptional},
{sfLedgerEntryType, SoeRequired},
{sfFlags, SoeRequired},
{sfSponsor, SoeOptional},
};
return kCommonFields;
}

View File

@@ -279,6 +279,14 @@ STTx::checkSign(Rules const& rules) const
if (auto const ret = checkSign(rules, counterSig); !ret)
return std::unexpected("Counterparty: " + ret.error());
}
if (isFieldPresent(sfSponsorSignature))
{
auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature);
if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret)
return std::unexpected("Sponsor: " + ret.error());
}
return {};
}

View File

@@ -106,6 +106,7 @@ transResults()
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -216,6 +217,7 @@ transResults()
MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."),
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
MAKE_ERROR(terLOCKED, "Fund is locked."),
MAKE_ERROR(terNO_SPONSORSHIP, "No sponsorship found."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
};

View File

@@ -30,6 +30,9 @@ TxFormats::getCommonFields()
{sfSigners, SoeOptional}, // submit_multisigned
{sfNetworkID, SoeOptional},
{sfDelegate, SoeOptional},
{sfSponsor, SoeOptional},
{sfSponsorFlags, SoeOptional},
{sfSponsorSignature, SoeOptional},
};
return kCommonFields;
}

View File

@@ -1,15 +1,47 @@
#include <xrpl/server/InfoSub.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/resource/Consumer.h>
#include <cstdint>
#include <exception>
#include <memory>
#include <mutex>
namespace xrpl {
namespace {
// Wraps a Source teardown call so that an exception from one cleanup
// step does not prevent the subsequent steps from running. Source methods
// acquire a lock and can throw std::system_error; a throw out of ~InfoSub
// during stack unwinding would terminate the process. Failures are
// reported through the Source's Journal so they reach the configured log
// sinks; JLOG itself cannot throw, so the noexcept guarantee holds.
template <typename F>
void
safeUnsub(std::uint64_t seq, F&& f, beast::Journal j) noexcept
{
try
{
f();
}
catch (std::exception const& e)
{
JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: " << e.what();
}
catch (...)
{
JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: unknown exception";
}
}
} // namespace
// This is the primary interface into the "client" portion of the program.
// Code that wants to do normal operations on the network such as
// creating and monitoring accounts, creating transactions, and so on
@@ -32,25 +64,44 @@ InfoSub::InfoSub(Source& source, Consumer consumer)
InfoSub::~InfoSub()
{
source_.unsubTransactions(seq_);
source_.unsubRTTransactions(seq_);
source_.unsubLedger(seq_);
source_.unsubManifests(seq_);
source_.unsubServer(seq_);
source_.unsubValidations(seq_);
source_.unsubPeerStatus(seq_);
source_.unsubConsensus(seq_);
// Each Source teardown call below acquires a server-side lock and
// can throw. Wrap each independent call so partial failure does not
// skip the remaining teardown steps.
auto const& j = source_.journal();
safeUnsub(seq_, [&] { source_.unsubTransactions(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubRTTransactions(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubLedger(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubManifests(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubServer(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubValidations(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubPeerStatus(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubConsensus(seq_); }, j);
// Use the internal unsubscribe so that it won't call
// back to us and modify its own parameter
if (!realTimeSubscriptions_.empty())
source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true);
{
safeUnsub(
seq_, [&] { source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true); }, j);
}
if (!normalSubscriptions_.empty())
source_.unsubAccountInternal(seq_, normalSubscriptions_, false);
{
safeUnsub(
seq_, [&] { source_.unsubAccountInternal(seq_, normalSubscriptions_, false); }, j);
}
for (auto const& account : accountHistorySubscriptions_)
source_.unsubAccountHistoryInternal(seq_, account, false);
{
safeUnsub(seq_, [&] { source_.unsubAccountHistoryInternal(seq_, account, false); }, j);
}
for (auto const& book : bookSubscriptions_)
{
safeUnsub(seq_, [&] { source_.unsubBookInternal(seq_, book); }, j);
}
}
Resource::Consumer&
@@ -114,6 +165,20 @@ InfoSub::deleteSubAccountHistory(AccountID const& account)
accountHistorySubscriptions_.erase(account);
}
void
InfoSub::insertBookSubscription(Book const& book)
{
std::scoped_lock const sl(lock_);
bookSubscriptions_.insert(book);
}
void
InfoSub::deleteBookSubscription(Book const& book)
{
std::scoped_lock const sl(lock_);
bookSubscriptions_.erase(book);
}
void
InfoSub::clearRequest()
{

View File

@@ -18,6 +18,7 @@
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -41,6 +42,7 @@
#include <xrpl/tx/apply.h>
#include <xrpl/tx/applySteps.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <exception>
@@ -164,6 +166,62 @@ preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::J
} // namespace detail
static NotTEC
preflight1Sponsor(PreflightContext const& ctx, AccountID const& id)
{
bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor);
bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags);
bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature);
if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor))
return temDISABLED;
if (hasSponsorFlags &&
((ctx.tx.getFieldU32(sfSponsorFlags) & ~(spfSponsorFee | spfSponsorReserve)) != 0u))
{
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
return temINVALID_FLAG;
}
if (!hasSponsor)
{
if (hasSponsorFlags)
{
JLOG(ctx.j.debug()) << "preflight1: sponsor flags without sponsor definition";
return temINVALID_FLAG;
}
if (hasSponsorSig)
{
JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition";
return temMALFORMED;
}
}
else if (hasSponsorFlags)
{
auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags);
if (((sponsorFlags & ~(spfSponsorFee | spfSponsorReserve)) != 0u) || sponsorFlags == 0)
{
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
return temINVALID_FLAG;
}
}
else
{
JLOG(ctx.j.debug()) << "preflight1: no sponsor flags";
return temINVALID_FLAG;
}
if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == id)
{
JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the "
"same as the transaction originator";
return temMALFORMED;
}
return tesSUCCESS;
}
/** Performs early sanity checks on the account and fee fields */
NotTEC
Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
@@ -215,6 +273,9 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
!ctx.rules.enabled(featureBatch),
"Inner batch transaction must have a parent batch ID.");
if (auto const ter = preflight1Sponsor(ctx, id); !isTesSuccess(ter))
return ter;
return tesSUCCESS;
}
@@ -310,6 +371,40 @@ Transactor::checkPermission(ReadView const& view, STTx const& tx)
return checkTxPermission(sle, tx);
}
NotTEC
Transactor::checkSponsor(ReadView const& view, STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsor))
return tesSUCCESS;
if (auto const sponsorSle = getTxReserveSponsor(view, tx); !sponsorSle)
return terNO_ACCOUNT;
auto const hasSponsorSignature = tx.isFieldPresent(sfSponsorSignature);
if (hasSponsorSignature)
return tesSUCCESS;
auto const sponsorshipSle =
view.read(keylet::sponsor(tx.getAccountID(sfSponsor), tx.getAccountID(sfAccount)));
// sponsorship object missing for pre-funded tx
if (!sponsorshipSle)
return terNO_SPONSORSHIP;
auto const sponsorFlags = tx.getFieldU32(sfSponsorFlags);
if (((sponsorFlags & spfSponsorFee) != 0u) &&
sponsorshipSle->isFlag(lsfSponsorshipRequireSignForFee))
return terNO_SPONSORSHIP;
if (((sponsorFlags & spfSponsorReserve) != 0u) &&
sponsorshipSle->isFlag(lsfSponsorshipRequireSignForReserve))
return terNO_SPONSORSHIP;
return tesSUCCESS;
}
XRPAmount
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
{
@@ -318,6 +413,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
// The computation has two parts:
// * The base fee, which is the same for most transactions.
// * The additional cost of each multisignature on the transaction.
// * The additional cost of each multisignature on the sponsor.
XRPAmount const baseFee = view.fees().base;
// Each signer adds one more baseFee to the minimum required fee
@@ -325,7 +421,15 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
std::size_t const signerCount =
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
return baseFee + (signerCount * baseFee);
std::size_t sponsorSignerCount = 0;
if (tx.isFieldPresent(sfSponsorSignature))
{
auto const sponsorObj = tx.getFieldObject(sfSponsorSignature);
sponsorSignerCount +=
sponsorObj.isFieldPresent(sfSigners) ? sponsorObj.getFieldArray(sfSigners).size() : 0;
}
return baseFee + ((signerCount + sponsorSignerCount) * baseFee);
}
// Returns the fee in fee units, not scaled for load.
@@ -395,12 +499,51 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
if (feePaid == beast::kZero)
return tesSUCCESS;
auto const id = ctx.tx.getFeePayer();
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
auto const feePayer = getFeePayer(ctx.view, ctx.tx);
auto const payerSle = ctx.view.read(feePayer.entry);
auto const balance = (*sle)[sfBalance].xrp();
if (!payerSle)
{
if (feePayer.type == FeePayerType::SponsorPreFunded)
{
// Sanity check: already checked in checkSponsor
return tefINTERNAL; // LCOV_EXCL_LINE
}
return terNO_ACCOUNT;
}
XRPAmount maxSpendable = beast::kZero;
if (feePayer.type == FeePayerType::SponsorPreFunded)
{
if (payerSle->getType() != ltSPONSORSHIP)
return tefINTERNAL; // LCOV_EXCL_LINE
if (payerSle->isFieldPresent(feePayer.balanceField))
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
if (payerSle->isFieldPresent(sfMaxFee))
{
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
maxSpendable = std::min(maxSpendable, cap);
}
}
else
{
if (payerSle->getType() != ltACCOUNT_ROOT)
return tefINTERNAL; // LCOV_EXCL_LINE
if (feePayer.type == FeePayerType::SponsorCoSigned)
{
STAmount const sponsorReserve = accountReserve(ctx.view, payerSle, ctx.j);
maxSpendable = payerSle->getFieldAmount(sfBalance).xrp() - sponsorReserve.xrp();
}
else
{
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
}
}
// NOTE: Because preclaim evaluates against a static readview, it
// does not reflect fee deductions from other transactions paid by
@@ -409,12 +552,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
// transactions, this check may pass optimistically.
// The fee shortfall will be handled by the Transactor::reset mechanism,
// which caps the fee to the remaining actual balance.
if (balance < feePaid)
if (maxSpendable < feePaid)
{
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance)
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable)
<< " paid=" << to_string(feePaid);
if ((balance > beast::kZero) && !ctx.view.open())
if ((maxSpendable > beast::kZero) && !ctx.view.open())
{
// Closed ledger, non-zero balance, less than fee
return tecINSUFF_FEE;
@@ -431,16 +574,27 @@ Transactor::payFee()
{
auto const feePaid = ctx_.tx[sfFee].xrp();
auto const feePayer = ctx_.tx.getFeePayer();
auto const sle = view().peek(keylet::account(feePayer));
auto const feePayer = getFeePayer(view(), ctx_.tx);
auto const sle = view().peek(feePayer.entry);
JLOG(j_.trace()) << "Fee payer: " + to_string(feePayer.entry.key);
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Deduct the fee, so it's not available during the transaction.
// Will only write the account back if the transaction succeeds.
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
if (feePayer != accountID_)
view().update(sle); // done in `apply()` for the account
auto const feeAmountAfter = sle->getFieldAmount(feePayer.balanceField) - feePaid;
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
{
// Because ltSponsorship.sfFeeAmount is soeOptional
sle->makeFieldAbsent(feePayer.balanceField);
}
else
{
sle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
}
view().update(sle);
// VFALCO Should we call view().rawDestroyXRP() here as well?
return tesSUCCESS;
@@ -614,7 +768,7 @@ Transactor::ticketDelete(
}
// Update the Ticket owner's reserve.
adjustOwnerCount(view, sleAccount, -1, j);
adjustOwnerCountObj(view, sleAccount, sleTicket, -1, j);
// Remove Ticket from ledger.
view.erase(sleTicket);
@@ -705,6 +859,22 @@ Transactor::checkSign(
return tesSUCCESS;
}
if (sigObject.isFieldPresent(sfSponsorSignature))
{
// Co-signed sponsorship
// Sanity check: already checked in preflight1
if (!sigObject.isFieldPresent(sfSponsor))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsorAccountID = sigObject.getAccountID(sfSponsor);
auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature);
if (auto const ret =
checkSign(view, flags, std::nullopt, sponsorAccountID, sponsorSignature, j);
!isTesSuccess(ret))
return ret;
}
// If the pk is empty and not simulate or simulate and signers,
// then we must be multi-signing.
if (sigObject.isFieldPresent(sfSigners))
@@ -1089,11 +1259,19 @@ Transactor::reset(XRPAmount fee)
if (!txnAcct)
return {tefINTERNAL, beast::kZero};
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
auto const feePayer = getFeePayer(view(), ctx_.tx);
auto const payerSle = view().peek(feePayer.entry);
if (!payerSle)
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
auto const balance = payerSle->getFieldAmount(feePayer.balanceField).xrp();
if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee))
{
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
fee = std::min(fee, cap);
}
// balance should have already been checked in checkFee / preFlight.
XRPL_ASSERT(
@@ -1112,7 +1290,17 @@ Transactor::reset(XRPAmount fee)
// If for some reason we are unable to consume the ticket or sequence
// then the ledger is corrupted. Rather than make things worse we
// reject the transaction.
payerSle->setFieldAmount(sfBalance, balance - fee);
auto const feeAmountAfter = balance - fee;
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
{
// Because ltSponsorship.sfFeeAmount is soeOptional
payerSle->makeFieldAbsent(feePayer.balanceField);
}
else
{
payerSle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
}
TER const ter{consumeSeqProxy(txnAcct)};
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
@@ -1126,6 +1314,40 @@ Transactor::reset(XRPAmount fee)
return {ter, fee};
}
FeePayer
Transactor::getFeePayer(ReadView const& view, STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && ((tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u))
{
auto const sponsorAccountID = tx.getAccountID(sfSponsor);
auto const sponseeAccountID = tx.getAccountID(sfAccount);
auto const hasSponsorSignature = tx.isFieldPresent(sfSponsorSignature);
auto const sponsorshipKeylet = keylet::sponsor(sponsorAccountID, sponseeAccountID);
// if pre-funded sponsorship exists, prefer it
if (hasSponsorSignature && !view.exists(sponsorshipKeylet))
{
// co-signed
return FeePayer{
.entry = keylet::account(sponsorAccountID),
.balanceField = sfBalance,
.type = FeePayerType::SponsorCoSigned};
}
// pre funded
return FeePayer{
.entry = sponsorshipKeylet,
.balanceField = sfFeeAmount,
.type = FeePayerType::SponsorPreFunded};
}
auto const payerAccountKeylet = keylet::account(tx.getFeePayer());
auto const payerType =
tx.isFieldPresent(sfDelegate) ? FeePayerType::Delegate : FeePayerType::Account;
return FeePayer{.entry = payerAccountKeylet, .balanceField = sfBalance, .type = payerType};
}
// The sole purpose of this function is to provide a convenient, named
// location to set a breakpoint, to be used when replaying transactions.
void

Some files were not shown because too many files have changed in this diff Show More