mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-24 05:32:29 +00:00
Compare commits
13 Commits
legleux/li
...
bthomee/io
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b55e344205 | ||
|
|
8a896f318a | ||
|
|
0e26aadfe6 | ||
|
|
d029bcf2d0 | ||
|
|
403cab41e9 | ||
|
|
59e6fbfe12 | ||
|
|
d63f80f73c | ||
|
|
dc5eb0ea50 | ||
|
|
609024f15c | ||
|
|
1bf5b0aa10 | ||
|
|
f783a15bc8 | ||
|
|
5a94948a04 | ||
|
|
f586382622 |
@@ -1,62 +0,0 @@
|
||||
name: Package DEB
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [pkg]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-package:
|
||||
runs-on: legleux/clang17_node
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# - name: Install system dependencies
|
||||
# run: |
|
||||
# sudo apt-get update -qq
|
||||
# sudo apt-get install -y --no-install-recommends \
|
||||
# debhelper \
|
||||
# ninja-build \
|
||||
# python3-pip
|
||||
|
||||
# - name: Install Conan
|
||||
# run: pip3 install conan
|
||||
|
||||
- name: Setup Conan
|
||||
run: |
|
||||
conan remote add --index 0 xrplf https://conan.ripplex.io
|
||||
conan profile detect
|
||||
|
||||
- name: Install Conan dependencies
|
||||
run: |
|
||||
conan install . \
|
||||
--build missing \
|
||||
--settings:all build_type=Debug \
|
||||
--options:host "&:xrpld=True" \
|
||||
--output-folder tc
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE=tc/build/generators/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_INSTALL_PREFIX=/opt/xrpld \
|
||||
-Dvoidstar=ON \
|
||||
-Dxrpld=ON \
|
||||
-Dtests=OFF
|
||||
|
||||
- name: Build xrpld
|
||||
run: cmake --build build --target xrpld --parallel $(nproc --ignore 3)
|
||||
|
||||
- name: Build DEB package
|
||||
run: cmake --build build --target package-deb
|
||||
|
||||
- name: Run install smoke test
|
||||
run: ctest --test-dir build -R install-deb -V
|
||||
|
||||
- name: Upload DEB artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: xrpld-deb
|
||||
path: build/debbuild/*.deb
|
||||
if-no-files-found: error
|
||||
@@ -177,6 +177,7 @@ test.unit_test > xrpl.protocol
|
||||
tests.libxrpl > xrpl.basics
|
||||
tests.libxrpl > xrpl.json
|
||||
tests.libxrpl > xrpl.net
|
||||
tests.libxrpl > xrpl.nodestore
|
||||
tests.libxrpl > xrpl.protocol
|
||||
tests.libxrpl > xrpl.protocol_autogen
|
||||
xrpl.conditions > xrpl.basics
|
||||
|
||||
@@ -298,7 +298,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
|
||||
@@ -133,7 +133,6 @@ endif()
|
||||
|
||||
include(XrplCore)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
if(tests)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
Source: rippled
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Michael Legleux <mlegleux@ripple.com>
|
||||
Rules-Requires-Root: no
|
||||
Build-Depends:
|
||||
debhelper-compat (= 13),
|
||||
Standards-Version: 4.7.0
|
||||
Homepage: https://github.com/XRPLF/rippled
|
||||
Vcs-Git: https://github.com/XRPLF/rippled.git
|
||||
Vcs-Browser: https://github.com/XRPLF/rippled
|
||||
|
||||
Package: rippled
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: any
|
||||
Depends:
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: XRP Ledger server daemon
|
||||
XRPL server daemon providing ledger validation and p2p network services.
|
||||
@@ -1,11 +0,0 @@
|
||||
opt/ripple/bin/rippled
|
||||
|
||||
opt/ripple/etc/rippled.cfg
|
||||
opt/ripple/etc/validators.txt
|
||||
|
||||
# systemd unit (if you keep it in debian/ as a source file, do NOT list it here)
|
||||
# If instead you stage it into debian/tmp, uncomment:
|
||||
# lib/systemd/system/rippled.service
|
||||
|
||||
usr/share/doc/rippled/README.md
|
||||
usr/share/doc/rippled/LICENSE.md
|
||||
@@ -1,8 +0,0 @@
|
||||
/opt/ripple/bin/rippled /usr/bin/rippled
|
||||
/opt/ripple/etc/rippled.cfg /etc/opt/ripple/xrpld.cfg
|
||||
/opt/ripple/etc/validators.txt /etc/opt/ripple/validators.txt
|
||||
|
||||
# TODO: Remove when rippled deprecated
|
||||
/opt/ripple/bin/rippled /opt/ripple/bin/xrpld
|
||||
/opt/ripple/etc/rippled.cfg /etc/opt/ripple/xrpld.cfg
|
||||
/opt/ripple /opt/xrpld
|
||||
@@ -1,15 +0,0 @@
|
||||
/var/log/rippled/*.log {
|
||||
daily
|
||||
minsize 200M
|
||||
rotate 7
|
||||
nocreate
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
compresscmd /usr/bin/nice
|
||||
compressoptions -n19 ionice -c3 gzip
|
||||
compressext .gz
|
||||
postrotate
|
||||
/opt/ripple/bin/rippled --conf /opt/ripple/etc/rippled.cfg logrotate
|
||||
endscript
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
[Unit]
|
||||
Description=XRPL daemon
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
|
||||
User=rippled
|
||||
Group=rippled
|
||||
|
||||
# Canonical config location (as you described)
|
||||
ExecStart=/opt/ripple/bin/rippled --conf /etc/opt/ripple/rippled.cfg
|
||||
|
||||
# Reasonable hardening defaults (trim if they break your runtime)
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectControlGroups=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
LockPersonality=true
|
||||
RestrictRealtime=true
|
||||
RestrictSUIDSGID=true
|
||||
MemoryDenyWriteExecute=true
|
||||
SystemCallArchitectures=native
|
||||
|
||||
# Allow writes only where you actually need them
|
||||
ReadWritePaths=/var/lib/rippled /var/log/rippled
|
||||
|
||||
StateDirectory=rippled
|
||||
LogsDirectory=rippled
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
TimeoutStopSec=30s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,5 +0,0 @@
|
||||
u rippled - "XRPL Daemon" /var/lib/ripled
|
||||
|
||||
# Type Name ID GECOS Home Shell
|
||||
g rippled - - - -
|
||||
u rippled - "XRPL rippled" /var/lib/rippled /usr/sbin/nologin
|
||||
@@ -1,3 +0,0 @@
|
||||
# StateDirectory/LogsDirectory/RuntimeDirectory in systemd service makes this redundant but this enables apt purge.
|
||||
d /var/lib/rippled 0750 rippled rippled -
|
||||
d /var/log/rippled 0750 rippled rippled -
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||
@:
|
||||
|
||||
override_dh_auto_install:
|
||||
rm -rf debian/tmp
|
||||
mkdir -p debian/tmp/opt/ripple/bin
|
||||
mkdir -p debian/tmp/opt/ripple/etc
|
||||
mkdir -p debian/tmp/usr
|
||||
cp -a "$(INSTALL_TREE)/bin" debian/tmp/opt/ripple
|
||||
cp -a "$(INSTALL_TREE)/etc" debian/tmp/opt/ripple
|
||||
cp -a "$(INSTALL_TREE)/usr" debian/tmp
|
||||
rm -rf debian/tmp/usr/include
|
||||
rm -rf debian/tmp/usr/lib
|
||||
install -Dm0644 README.md debian/tmp/usr/share/doc/rippled/README.md
|
||||
install -Dm0644 LICENSE.md debian/tmp/usr/share/doc/rippled/LICENSE.md
|
||||
install -Dm0644 cfg/rippled-example.cfg debian/tmp/opt/ripple/etc/rippled.cfg
|
||||
install -Dm0644 cfg/validators-example.txt debian/tmp/opt/ripple/etc/validators.txt
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd
|
||||
|
||||
override_dh_installsysusers:
|
||||
dh_installsysusers
|
||||
|
||||
override_dh_installtmpfiles:
|
||||
dh_installtmpfiles
|
||||
|
||||
override_dh_install:
|
||||
dh_install
|
||||
|
||||
override_dh_dwz:
|
||||
@:
|
||||
|
||||
override_dh_strip:
|
||||
dh_strip --no-automatic-dbgsym
|
||||
@@ -1,144 +0,0 @@
|
||||
#[===================================================================[
|
||||
Linux packaging support: RPM and Debian targets + install tests
|
||||
#]===================================================================]
|
||||
|
||||
if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/opt/xrpld")
|
||||
message(
|
||||
STATUS
|
||||
"Packaging targets require -DCMAKE_INSTALL_PREFIX=/opt/xrpld "
|
||||
"(current: '${CMAKE_INSTALL_PREFIX}'); skipping."
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Generate the RPM spec from template (substitutes @xrpld_version@).
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/package/rpm/xrpld.spec.in
|
||||
${CMAKE_BINARY_DIR}/package/rpm/xrpld.spec
|
||||
@ONLY
|
||||
)
|
||||
|
||||
find_program(RPMBUILD_EXECUTABLE rpmbuild)
|
||||
if(RPMBUILD_EXECUTABLE)
|
||||
add_custom_target(
|
||||
package-rpm
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/package/build-pkg.sh rpm ${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_BINARY_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Building RPM package"
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(STATUS "rpmbuild not found; 'package-rpm' target not available")
|
||||
endif()
|
||||
|
||||
find_program(DPKG_BUILDPACKAGE_EXECUTABLE dpkg-buildpackage)
|
||||
if(DPKG_BUILDPACKAGE_EXECUTABLE)
|
||||
add_custom_target(
|
||||
package-deb
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/package/build-pkg.sh deb ${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_BINARY_DIR} ${xrpld_version}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Building Debian package"
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(
|
||||
STATUS
|
||||
"dpkg-buildpackage not found; 'package-deb' target not available"
|
||||
)
|
||||
endif()
|
||||
|
||||
#[===================================================================[
|
||||
CTest fixtures for package install verification (requires docker)
|
||||
#]===================================================================]
|
||||
|
||||
find_program(DOCKER_EXECUTABLE docker)
|
||||
if(NOT DOCKER_EXECUTABLE)
|
||||
message(STATUS "docker not found; package install tests not available")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(DEB_TEST_IMAGE "geerlingguy/docker-ubuntu2204-ansible:latest")
|
||||
set(RPM_TEST_IMAGE "geerlingguy/docker-rockylinux9-ansible:latest")
|
||||
|
||||
foreach(PKG deb rpm)
|
||||
if(PKG STREQUAL "deb")
|
||||
set(IMAGE ${DEB_TEST_IMAGE})
|
||||
else()
|
||||
set(IMAGE ${RPM_TEST_IMAGE})
|
||||
endif()
|
||||
|
||||
# Fixture: start container
|
||||
add_test(
|
||||
NAME ${PKG}_container_start
|
||||
COMMAND
|
||||
sh -c
|
||||
"docker rm -f xrpld_${PKG}_install_test 2>/dev/null || true && \
|
||||
docker run --rm -d \
|
||||
--name xrpld_${PKG}_install_test \
|
||||
--memory=45g --memory-swap=45g \
|
||||
--privileged \
|
||||
--cgroupns host \
|
||||
--volume '${CMAKE_SOURCE_DIR}:/root:ro' \
|
||||
--volume /sys/fs/cgroup:/sys/fs/cgroup:rw \
|
||||
--tmpfs /tmp --tmpfs /run --tmpfs /run/lock \
|
||||
${IMAGE} \
|
||||
/usr/sbin/init"
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_container_start
|
||||
PROPERTIES FIXTURES_SETUP ${PKG}_container LABELS packaging
|
||||
)
|
||||
|
||||
# Fixture: stop container
|
||||
# On CI: always stop. Locally: leave running on failure for diagnosis.
|
||||
add_test(
|
||||
NAME ${PKG}_container_stop
|
||||
COMMAND
|
||||
sh -c
|
||||
"if [ -n \"$CI\" ] || ! docker exec xrpld_${PKG}_install_test test -f /tmp/test_failed 2>/dev/null; then \
|
||||
docker rm -f xrpld_${PKG}_install_test; \
|
||||
else \
|
||||
echo 'Tests failed — leaving xrpld_${PKG}_install_test running for diagnosis'; \
|
||||
echo 'Clean up with: docker rm -f xrpld_${PKG}_install_test'; \
|
||||
fi"
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_container_stop
|
||||
PROPERTIES FIXTURES_CLEANUP ${PKG}_container LABELS packaging
|
||||
)
|
||||
|
||||
# Install package and run smoke test
|
||||
add_test(
|
||||
NAME ${PKG}_install
|
||||
COMMAND
|
||||
docker exec -w /root xrpld_${PKG}_install_test bash
|
||||
/root/package/test/smoketest.sh local
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_install
|
||||
PROPERTIES
|
||||
FIXTURES_REQUIRED ${PKG}_container
|
||||
FIXTURES_SETUP ${PKG}_installed
|
||||
LABELS packaging
|
||||
TIMEOUT 600
|
||||
)
|
||||
|
||||
# Validate install paths and compat symlinks
|
||||
add_test(
|
||||
NAME ${PKG}_install_paths
|
||||
COMMAND
|
||||
docker exec -w /root xrpld_${PKG}_install_test sh
|
||||
/root/package/test/check_install_paths.sh
|
||||
)
|
||||
set_tests_properties(
|
||||
${PKG}_install_paths
|
||||
PROPERTIES
|
||||
FIXTURES_REQUIRED "${PKG}_container;${PKG}_installed"
|
||||
LABELS packaging
|
||||
TIMEOUT 60
|
||||
)
|
||||
endforeach()
|
||||
@@ -75,12 +75,6 @@ words:
|
||||
- confs
|
||||
- connectability
|
||||
- coro
|
||||
- missingok
|
||||
- noreplace
|
||||
- notifempty
|
||||
- preun
|
||||
- shlibs
|
||||
- SRPMS
|
||||
- coros
|
||||
- cowid
|
||||
- cryptocondition
|
||||
@@ -104,7 +98,6 @@ words:
|
||||
- dxrpl
|
||||
- endmacro
|
||||
- exceptioned
|
||||
- EXPECT_STREQ
|
||||
- Falco
|
||||
- fcontext
|
||||
- finalizers
|
||||
@@ -275,8 +268,8 @@ words:
|
||||
- txn
|
||||
- txns
|
||||
- txs
|
||||
- ubsan
|
||||
- UBSAN
|
||||
- ubsan
|
||||
- umant
|
||||
- unacquired
|
||||
- unambiguity
|
||||
@@ -312,6 +305,7 @@ words:
|
||||
- xbridge
|
||||
- xchain
|
||||
- ximinez
|
||||
- EXPECT_STREQ
|
||||
- XMACRO
|
||||
- xrpkuwait
|
||||
- xrpl
|
||||
@@ -319,4 +313,3 @@ words:
|
||||
- xrplf
|
||||
- xxhash
|
||||
- xxhasher
|
||||
- Zgzip
|
||||
|
||||
43
include/xrpl/ledger/Credit.h
Normal file
43
include/xrpl/ledger/Credit.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Calculate the maximum amount of IOUs that an account can hold
|
||||
@param ledger the ledger to check against.
|
||||
@param account the account of interest.
|
||||
@param issuer the issuer of the IOU.
|
||||
@param currency the IOU to check.
|
||||
@return The maximum amount that can be held.
|
||||
*/
|
||||
/** @{ */
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency);
|
||||
|
||||
IOUAmount
|
||||
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur);
|
||||
/** @} */
|
||||
|
||||
/** Returns the amount of IOUs issued by issuer that are held by an account
|
||||
@param ledger the ledger to check against.
|
||||
@param account the account of interest.
|
||||
@param issuer the issuer of the IOU.
|
||||
@param currency the IOU to check.
|
||||
*/
|
||||
/** @{ */
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency);
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -4,13 +4,6 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
@@ -28,6 +21,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
enum class WaiveTransferFee : bool { No = false, Yes };
|
||||
enum class SkipEntry : bool { No = false, Yes };
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -60,6 +54,24 @@ enum class SkipEntry : bool { No = false, Yes };
|
||||
[[nodiscard]] bool
|
||||
hasExpired(ReadView const& view, std::optional<std::uint32_t> const& exp);
|
||||
|
||||
/** Controls the treatment of frozen account balances */
|
||||
enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN };
|
||||
|
||||
/** Controls the treatment of unauthorized MPT balances */
|
||||
enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED };
|
||||
|
||||
/** Controls whether to include the account's full spendable balance */
|
||||
enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE };
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
// Note, depth parameter is used to limit the recursion depth
|
||||
[[nodiscard]] bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
@@ -68,6 +80,175 @@ isVaultPseudoAccountFrozen(
|
||||
MPTIssue const& mptShare,
|
||||
int depth);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isIndividualFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int = 0 /*ignored*/)
|
||||
{
|
||||
return isFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
{
|
||||
return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return checkFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Issue const& issue)
|
||||
{
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isFrozen(view, account, issue.currency, issue.account))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Asset const& asset,
|
||||
int depth = 0)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
return isAnyFrozen(view, accounts, issue);
|
||||
else
|
||||
return isAnyFrozen(view, accounts, issue, depth);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
int = 0 /*ignored*/)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0)
|
||||
{
|
||||
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
|
||||
// funds, so checking "deep frozen" is the same as checking "frozen".
|
||||
return isFrozen(view, account, mptIssue, depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] inline bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
|
||||
asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
{
|
||||
return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return checkDeepFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isLPTokenFrozen(
|
||||
ReadView const& view,
|
||||
@@ -75,6 +256,159 @@ isLPTokenFrozen(
|
||||
Issue const& asset,
|
||||
Issue const& asset2);
|
||||
|
||||
// Returns the amount an account can spend.
|
||||
//
|
||||
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
|
||||
// without going into debt.
|
||||
//
|
||||
// If shFULL_BALANCE is specified, this is the amount the account can spend
|
||||
// total. Specifically:
|
||||
// * The account can go into debt if using a trust line, and the other side has
|
||||
// a non-zero limit.
|
||||
// * If the account is the asset issuer the limit is defined by the asset /
|
||||
// issuance.
|
||||
//
|
||||
// <-- saAmount: amount of currency held by account. May be negative.
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Asset const& asset,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
// Returns the amount an account can spend of the currency type saDefault, or
|
||||
// returns saDefault if this account is the issuer of the currency in
|
||||
// question. Should be used in favor of accountHolds when questioning how much
|
||||
// an account can spend while also allowing currency issuers to spend
|
||||
// unlimited amounts of their own currency (since they can always issue more).
|
||||
[[nodiscard]] STAmount
|
||||
accountFunds(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j);
|
||||
|
||||
// Return the account's liquid (not reserved) XRP. Generally prefer
|
||||
// calling accountHolds() over this interface. However, this interface
|
||||
// allows the caller to temporarily adjust the owner count should that be
|
||||
// necessary.
|
||||
//
|
||||
// @param ownerCountAdj positive to add to count, negative to reduce count.
|
||||
[[nodiscard]] XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
|
||||
|
||||
/** Iterate all items in the given directory. */
|
||||
void
|
||||
forEachItem(
|
||||
ReadView const& view,
|
||||
Keylet const& root,
|
||||
std::function<void(std::shared_ptr<SLE const> const&)> const& f);
|
||||
|
||||
/** Iterate all items after an item in the given directory.
|
||||
@param after The key of the item to start after
|
||||
@param hint The directory page containing `after`
|
||||
@param limit The maximum number of items to return
|
||||
@return `false` if the iteration failed
|
||||
*/
|
||||
bool
|
||||
forEachItemAfter(
|
||||
ReadView const& view,
|
||||
Keylet const& root,
|
||||
uint256 const& after,
|
||||
std::uint64_t const hint,
|
||||
unsigned int limit,
|
||||
std::function<bool(std::shared_ptr<SLE const> const&)> const& f);
|
||||
|
||||
/** Iterate all items in an account's owner directory. */
|
||||
inline void
|
||||
forEachItem(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
std::function<void(std::shared_ptr<SLE const> const&)> const& f)
|
||||
{
|
||||
return forEachItem(view, keylet::ownerDir(id), f);
|
||||
}
|
||||
|
||||
/** Iterate all items after an item in an owner directory.
|
||||
@param after The key of the item to start after
|
||||
@param hint The directory page containing `after`
|
||||
@param limit The maximum number of items to return
|
||||
@return `false` if the iteration failed
|
||||
*/
|
||||
inline bool
|
||||
forEachItemAfter(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
uint256 const& after,
|
||||
std::uint64_t const hint,
|
||||
unsigned int limit,
|
||||
std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
|
||||
{
|
||||
return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f);
|
||||
}
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuer The IOU issuer
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, AccountID const& issuer);
|
||||
|
||||
/** Returns MPT transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, MPTID const& issuanceID);
|
||||
|
||||
/** Returns the transfer fee as Rate based on the type of token
|
||||
* @param view The ledger view
|
||||
* @param amount The amount to transfer
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount);
|
||||
|
||||
/** Returns `true` if the directory is empty
|
||||
@param key The key of the directory
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
dirIsEmpty(ReadView const& view, Keylet const& k);
|
||||
|
||||
// Return the list of enabled amendments
|
||||
[[nodiscard]] std::set<uint256>
|
||||
getEnabledAmendments(ReadView const& view);
|
||||
@@ -140,6 +474,81 @@ areCompatible(
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Adjust the owner count up or down. */
|
||||
void
|
||||
adjustOwnerCount(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j);
|
||||
|
||||
/** @{ */
|
||||
/** Returns the first entry in the directory, advancing the index
|
||||
|
||||
@deprecated These are legacy function that are considered deprecated
|
||||
and will soon be replaced with an iterator-based model
|
||||
that is easier to use. You should not use them in new code.
|
||||
|
||||
@param view The view against which to operate
|
||||
@param root The root (i.e. first page) of the directory to iterate
|
||||
@param page The current page
|
||||
@param index The index inside the current page
|
||||
@param entry The entry at the current index
|
||||
|
||||
@return true if the directory isn't empty; false otherwise
|
||||
*/
|
||||
bool
|
||||
cdirFirst(
|
||||
ReadView const& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE const>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
|
||||
bool
|
||||
dirFirst(
|
||||
ApplyView& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
/** @} */
|
||||
|
||||
/** @{ */
|
||||
/** Returns the next entry in the directory, advancing the index
|
||||
|
||||
@deprecated These are legacy function that are considered deprecated
|
||||
and will soon be replaced with an iterator-based model
|
||||
that is easier to use. You should not use them in new code.
|
||||
|
||||
@param view The view against which to operate
|
||||
@param root The root (i.e. first page) of the directory to iterate
|
||||
@param page The current page
|
||||
@param index The index inside the current page
|
||||
@param entry The entry at the current index
|
||||
|
||||
@return true if the directory isn't empty; false otherwise
|
||||
*/
|
||||
bool
|
||||
cdirNext(
|
||||
ReadView const& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE const>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
|
||||
bool
|
||||
dirNext(
|
||||
ApplyView& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
/** @} */
|
||||
|
||||
[[nodiscard]] std::function<void(SLE::ref)>
|
||||
describeOwnerDir(AccountID const& account);
|
||||
|
||||
[[nodiscard]] TER
|
||||
dirLink(
|
||||
ApplyView& view,
|
||||
@@ -147,6 +556,63 @@ dirLink(
|
||||
std::shared_ptr<SLE>& object,
|
||||
SF_UINT64 const& node = sfOwnerNode);
|
||||
|
||||
AccountID
|
||||
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
|
||||
|
||||
/**
|
||||
*
|
||||
* Create pseudo-account, storing pseudoOwnerKey into ownerField.
|
||||
*
|
||||
* The list of valid ownerField is maintained in View.cpp and the caller to
|
||||
* this function must perform necessary amendment check(s) before using a
|
||||
* field. The amendment check is **not** performed in createPseudoAccount.
|
||||
*/
|
||||
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
|
||||
|
||||
// Returns true if and only if sleAcct is a pseudo-account or specific
|
||||
// pseudo-accounts in pseudoFieldFilter.
|
||||
//
|
||||
// Returns false if sleAcct is
|
||||
// * NOT a pseudo-account OR
|
||||
// * NOT a ltACCOUNT_ROOT OR
|
||||
// * null pointer
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(
|
||||
std::shared_ptr<SLE const> sleAcct,
|
||||
std::set<SField const*> const& pseudoFieldFilter = {});
|
||||
|
||||
// Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if
|
||||
// set
|
||||
// Pseudo-account designator fields MUST be maintained by including the
|
||||
// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
|
||||
// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
|
||||
// since a non-active amendment will not set any field, by definition.
|
||||
// Specific properties of a pseudo-account are NOT checked here, that's what
|
||||
// InvariantCheck is for.
|
||||
[[nodiscard]] std::vector<SField const*> const&
|
||||
getPseudoAccountFields();
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isPseudoAccount(
|
||||
ReadView const& view,
|
||||
AccountID const& accountId,
|
||||
std::set<SField const*> const& pseudoFieldFilter = {})
|
||||
{
|
||||
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, Asset const& asset);
|
||||
|
||||
/** Validates that the destination SLE and tag are valid
|
||||
|
||||
- Checks that the SLE is not null.
|
||||
- If the SLE requires a destination tag, checks that there is a tag.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
|
||||
|
||||
/** Checks that can withdraw funds from an object to itself or a destination.
|
||||
*
|
||||
* The receiver may be either the submitting account (sfAccount) or a different
|
||||
@@ -220,6 +686,351 @@ doWithdraw(
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
/// Any transactors that call addEmptyHolding() in doApply must call
|
||||
/// canAddHolding() in preflight with the same View and Asset
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags = 0,
|
||||
std::optional<AccountID> holderID = std::nullopt);
|
||||
|
||||
// VFALCO NOTE Both STAmount parameters should just
|
||||
// be "Amount", a unit-less number.
|
||||
//
|
||||
/** Create a trust line
|
||||
|
||||
This can set an initial balance.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // --> ripple state entry
|
||||
SLE::ref sleAccount, // --> the account being set.
|
||||
bool const bAuth, // --> authorize account.
|
||||
bool const bNoRipple, // --> others cannot ripple through
|
||||
bool const bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uSrcQualityIn,
|
||||
std::uint32_t uSrcQualityOut,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return removeEmptyHolding(view, accountID, issue, journal);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
trustDelete(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
/** Delete an offer.
|
||||
|
||||
Requirements:
|
||||
The passed `sle` be obtained from a prior
|
||||
call to view.peek()
|
||||
*/
|
||||
// [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile.
|
||||
TER
|
||||
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//
|
||||
// Money Transfers
|
||||
//
|
||||
|
||||
// Direct send w/o fees:
|
||||
// - Redeeming IOUs and/or sending sender's own IOUs.
|
||||
// - Create trust line of needed.
|
||||
// --> bCheckIssuer : normally require issuer to be involved.
|
||||
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
|
||||
|
||||
/** Calls static rippleCreditIOU if saAmount represents Issue.
|
||||
* Calls static rippleCreditMPT if saAmount represents MPTIssue.
|
||||
*/
|
||||
TER
|
||||
rippleCredit(
|
||||
ApplyView& view,
|
||||
AccountID const& uSenderID,
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
bool bCheckIssuer,
|
||||
beast::Journal j);
|
||||
|
||||
TER
|
||||
rippleLockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& uGrantorID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j);
|
||||
|
||||
TER
|
||||
rippleUnlockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& uGrantorID,
|
||||
AccountID const& uGranteeID,
|
||||
STAmount const& netAmount,
|
||||
STAmount const& grossAmount,
|
||||
beast::Journal j);
|
||||
|
||||
/** Calls static accountSendIOU if saAmount represents Issue.
|
||||
* Calls static accountSendMPT if saAmount represents MPTIssue.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
accountSend(
|
||||
ApplyView& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
WaiveTransferFee waiveFee = WaiveTransferFee::No);
|
||||
|
||||
using MultiplePaymentDestinations = std::vector<std::pair<AccountID, Number>>;
|
||||
/** Like accountSend, except one account is sending multiple payments (with the
|
||||
* same asset!) simultaneously
|
||||
*
|
||||
* Calls static accountSendMultiIOU if saAmount represents Issue.
|
||||
* Calls static accountSendMultiMPT if saAmount represents MPTIssue.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
accountSendMulti(
|
||||
ApplyView& view,
|
||||
AccountID const& senderID,
|
||||
Asset const& asset,
|
||||
MultiplePaymentDestinations const& receivers,
|
||||
beast::Journal j,
|
||||
WaiveTransferFee waiveFee = WaiveTransferFee::No);
|
||||
|
||||
[[nodiscard]] TER
|
||||
issueIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
redeemIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
transferXRP(
|
||||
ApplyView& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
|
||||
* - StrongAuth - before checking if authorization is required
|
||||
* - WeakAuth
|
||||
* for MPT - after checking lsfMPTRequireAuth flag
|
||||
* for IOU - do not check if trust line exists
|
||||
* - Legacy
|
||||
* for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
|
||||
* for IOU - do not check if trust line exists i.e. same as WeakAuth
|
||||
*/
|
||||
enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||
|
||||
/** Check if the account lacks required authorization.
|
||||
*
|
||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||
* and tesSUCCESS otherwise.
|
||||
*
|
||||
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
|
||||
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
|
||||
* RippleState does exist, and the RippleState is not authorized.
|
||||
*
|
||||
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
|
||||
* RippleState exists, and is not authorized. Return tecNO_LINE if
|
||||
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
|
||||
* WeakAuth and lsfRequireAuth is *not* set, this function will return
|
||||
* tesSUCCESS even if RippleState does *not* exist.
|
||||
*
|
||||
* The default "Legacy" auth type is equivalent to WeakAuth.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
Issue const& issue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy);
|
||||
|
||||
/** Check if the account lacks required authorization.
|
||||
*
|
||||
* This will also check for expired credentials. If it is called directly
|
||||
* from preclaim, the user should convert result tecEXPIRED to tesSUCCESS and
|
||||
* proceed to also check permissions with enforceMPTokenAuthorization inside
|
||||
* doApply. This will ensure that any expired credentials are deleted.
|
||||
*
|
||||
* requireAuth check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*
|
||||
* If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or
|
||||
* lsfMPTRequireAuth is set and MPToken is not authorized. Vault and LoanBroker
|
||||
* pseudo-accounts are implicitly authorized.
|
||||
*
|
||||
* If WeakAuth then return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken
|
||||
* doesn't exist or is not authorized (explicitly or via credentials, if
|
||||
* DomainID is set in MPTokenIssuance). Consequently, if WeakAuth and
|
||||
* lsfMPTRequireAuth is *not* set, this function will return true even if
|
||||
* MPToken does *not* exist.
|
||||
*
|
||||
* The default "Legacy" auth type is equivalent to StrongAuth.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy,
|
||||
int depth = 0);
|
||||
|
||||
[[nodiscard]] TER inline requireAuth(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue_) {
|
||||
return requireAuth(view, issue_, account, authType);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
/** Enforce account has MPToken to match its authorization.
|
||||
*
|
||||
* Called from doApply - it will check for expired (and delete if found any)
|
||||
* credentials matching DomainID set in MPTokenIssuance. Must be called if
|
||||
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim,
|
||||
* which implies that preclaim should replace `tecEXPIRED` with `tesSUCCESS`
|
||||
* in order for the transactor to proceed to doApply.
|
||||
*
|
||||
* This function will create MPToken (if needed) on the basis of any
|
||||
* non-expired credentials and will delete any expired credentials, indirectly
|
||||
* via verifyValidDomain, as per DomainID (if set in MPTokenIssuance).
|
||||
*
|
||||
* The caller does NOT need to ensure that DomainID is actually set - this
|
||||
* function handles gracefully both cases when DomainID is set and when not.
|
||||
*
|
||||
* The caller does NOT need to look for existing MPToken to match
|
||||
* mptIssue/account - this function checks lsfMPTAuthorized of an existing
|
||||
* MPToken iff DomainID is not set.
|
||||
*
|
||||
* Do not use for accounts which hold implied permission e.g. object owners or
|
||||
* if MPTokenIssuance does not require authorization. In both cases use
|
||||
* MPTokenAuthorize::authorize if MPToken does not yet exist.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal j);
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive MPT. Return tecNO_AUTH if it doesn't
|
||||
* and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
|
||||
[[nodiscard]] TER
|
||||
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
|
||||
|
||||
[[nodiscard]] TER inline canTransfer(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return canTransfer(view, issue, from, to);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
/** Deleter function prototype. Returns the status of the entry deletion
|
||||
* (if should not be skipped) and if the entry should be skipped. The status
|
||||
* is always tesSUCCESS if the entry should be skipped.
|
||||
@@ -241,6 +1052,57 @@ cleanupOnAccountDelete(
|
||||
beast::Journal j,
|
||||
std::optional<std::uint16_t> maxNodesToDelete = std::nullopt);
|
||||
|
||||
/** Delete trustline to AMM. The passed `sle` must be obtained from a prior
|
||||
* call to view.peek(). Fail if neither side of the trustline is AMM or
|
||||
* if ammAccountID is seated and is not one of the trustline's side.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
deleteAMMTrustLine(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleState,
|
||||
std::optional<AccountID> const& ammAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
// From the perspective of a vault, return the number of shares to give the
|
||||
// depositor when they deposit a fixed amount of assets. Since shares are MPT
|
||||
// this number is integral and always truncated in this calculation.
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets);
|
||||
|
||||
// From the perspective of a vault, return the number of assets to take from
|
||||
// depositor when they receive a fixed amount of shares. Note, since shares are
|
||||
// MPT, they are always an integral number.
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares);
|
||||
|
||||
enum class TruncateShares : bool { no = false, yes = true };
|
||||
|
||||
// From the perspective of a vault, return the number of shares to demand from
|
||||
// the depositor when they ask to withdraw a fixed amount of assets. Since
|
||||
// shares are MPT this number is integral, and it will be rounded to nearest
|
||||
// unless explicitly requested to be truncated instead.
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets,
|
||||
TruncateShares truncate = TruncateShares::no);
|
||||
|
||||
// From the perspective of a vault, return the number of assets to give the
|
||||
// depositor when they redeem a fixed amount of shares. Note, since shares are
|
||||
// MPT, they are always an integral number.
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares);
|
||||
|
||||
/** Has the specified time passed?
|
||||
|
||||
@param now the current time
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Check if the issuer has the global freeze flag set.
|
||||
@param issuer The account to check
|
||||
@return true if the account has global freeze set
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
|
||||
|
||||
// Calculate liquid XRP balance for an account.
|
||||
// This function may be used to calculate the amount of XRP that
|
||||
// the holder is able to freely spend. It subtracts reserve requirements.
|
||||
//
|
||||
// ownerCountAdj adjusts the owner count in case the caller calculates
|
||||
// before ledger entries are added or removed. Positive to add, negative
|
||||
// to subtract.
|
||||
//
|
||||
// @param ownerCountAdj positive to add to count, negative to reduce count.
|
||||
[[nodiscard]] XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
|
||||
|
||||
/** Adjust the owner count up or down. */
|
||||
void
|
||||
adjustOwnerCount(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j);
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuer The IOU issuer
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, AccountID const& issuer);
|
||||
|
||||
/** Generate a pseudo-account address from a pseudo owner key.
|
||||
@param pseudoOwnerKey The key to generate the address from
|
||||
@return The generated account ID
|
||||
*/
|
||||
AccountID
|
||||
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
|
||||
|
||||
/** Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account
|
||||
if set.
|
||||
|
||||
The list is constructed during initialization and is const after that.
|
||||
Pseudo-account designator fields MUST be maintained by including the
|
||||
SField::sMD_PseudoAccount flag in the SField definition.
|
||||
*/
|
||||
[[nodiscard]] std::vector<SField const*> const&
|
||||
getPseudoAccountFields();
|
||||
|
||||
/** Returns true if and only if sleAcct is a pseudo-account or specific
|
||||
pseudo-accounts in pseudoFieldFilter.
|
||||
|
||||
Returns false if sleAcct is:
|
||||
- NOT a pseudo-account OR
|
||||
- NOT a ltACCOUNT_ROOT OR
|
||||
- null pointer
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(
|
||||
std::shared_ptr<SLE const> sleAcct,
|
||||
std::set<SField const*> const& pseudoFieldFilter = {});
|
||||
|
||||
/** Convenience overload that reads the account from the view. */
|
||||
[[nodiscard]] inline bool
|
||||
isPseudoAccount(
|
||||
ReadView const& view,
|
||||
AccountID const& accountId,
|
||||
std::set<SField const*> const& pseudoFieldFilter = {})
|
||||
{
|
||||
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pseudo-account, storing pseudoOwnerKey into ownerField.
|
||||
*
|
||||
* The list of valid ownerField is maintained in AccountRootHelpers.cpp and
|
||||
* the caller to this function must perform necessary amendment check(s)
|
||||
* before using a field. The amendment check is **not** performed in
|
||||
* createPseudoAccount.
|
||||
*/
|
||||
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
|
||||
|
||||
/** Checks the destination and tag.
|
||||
|
||||
- Checks that the SLE is not null.
|
||||
- If the SLE requires a destination tag, checks that there is a tag.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,223 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <
|
||||
class V,
|
||||
class N,
|
||||
class = std::enable_if_t<
|
||||
std::is_same_v<std::remove_cv_t<N>, SLE> && std::is_base_of_v<ReadView, V>>>
|
||||
bool
|
||||
internalDirNext(
|
||||
V& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<N>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry)
|
||||
{
|
||||
auto const& svIndexes = page->getFieldV256(sfIndexes);
|
||||
XRPL_ASSERT(index <= svIndexes.size(), "xrpl::detail::internalDirNext : index inside range");
|
||||
|
||||
if (index >= svIndexes.size())
|
||||
{
|
||||
auto const next = page->getFieldU64(sfIndexNext);
|
||||
|
||||
if (!next)
|
||||
{
|
||||
entry.zero();
|
||||
return false;
|
||||
}
|
||||
|
||||
if constexpr (std::is_const_v<N>)
|
||||
{
|
||||
page = view.read(keylet::page(root, next));
|
||||
}
|
||||
else
|
||||
{
|
||||
page = view.peek(keylet::page(root, next));
|
||||
}
|
||||
|
||||
XRPL_ASSERT(page, "xrpl::detail::internalDirNext : non-null root");
|
||||
|
||||
if (!page)
|
||||
return false;
|
||||
|
||||
index = 0;
|
||||
|
||||
return internalDirNext(view, root, page, index, entry);
|
||||
}
|
||||
|
||||
entry = svIndexes[index++];
|
||||
return true;
|
||||
}
|
||||
|
||||
template <
|
||||
class V,
|
||||
class N,
|
||||
class = std::enable_if_t<
|
||||
std::is_same_v<std::remove_cv_t<N>, SLE> && std::is_base_of_v<ReadView, V>>>
|
||||
bool
|
||||
internalDirFirst(
|
||||
V& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<N>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry)
|
||||
{
|
||||
if constexpr (std::is_const_v<N>)
|
||||
{
|
||||
page = view.read(keylet::page(root));
|
||||
}
|
||||
else
|
||||
{
|
||||
page = view.peek(keylet::page(root));
|
||||
}
|
||||
|
||||
if (!page)
|
||||
return false;
|
||||
|
||||
index = 0;
|
||||
|
||||
return internalDirNext(view, root, page, index, entry);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** @{ */
|
||||
/** Returns the first entry in the directory, advancing the index
|
||||
|
||||
@deprecated These are legacy function that are considered deprecated
|
||||
and will soon be replaced with an iterator-based model
|
||||
that is easier to use. You should not use them in new code.
|
||||
|
||||
@param view The view against which to operate
|
||||
@param root The root (i.e. first page) of the directory to iterate
|
||||
@param page The current page
|
||||
@param index The index inside the current page
|
||||
@param entry The entry at the current index
|
||||
|
||||
@return true if the directory isn't empty; false otherwise
|
||||
*/
|
||||
bool
|
||||
cdirFirst(
|
||||
ReadView const& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE const>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
|
||||
bool
|
||||
dirFirst(
|
||||
ApplyView& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
/** @} */
|
||||
|
||||
/** @{ */
|
||||
/** Returns the next entry in the directory, advancing the index
|
||||
|
||||
@deprecated These are legacy function that are considered deprecated
|
||||
and will soon be replaced with an iterator-based model
|
||||
that is easier to use. You should not use them in new code.
|
||||
|
||||
@param view The view against which to operate
|
||||
@param root The root (i.e. first page) of the directory to iterate
|
||||
@param page The current page
|
||||
@param index The index inside the current page
|
||||
@param entry The entry at the current index
|
||||
|
||||
@return true if the directory isn't empty; false otherwise
|
||||
*/
|
||||
bool
|
||||
cdirNext(
|
||||
ReadView const& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE const>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
|
||||
bool
|
||||
dirNext(
|
||||
ApplyView& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry);
|
||||
/** @} */
|
||||
|
||||
/** Iterate all items in the given directory. */
|
||||
void
|
||||
forEachItem(
|
||||
ReadView const& view,
|
||||
Keylet const& root,
|
||||
std::function<void(std::shared_ptr<SLE const> const&)> const& f);
|
||||
|
||||
/** Iterate all items after an item in the given directory.
|
||||
@param after The key of the item to start after
|
||||
@param hint The directory page containing `after`
|
||||
@param limit The maximum number of items to return
|
||||
@return `false` if the iteration failed
|
||||
*/
|
||||
bool
|
||||
forEachItemAfter(
|
||||
ReadView const& view,
|
||||
Keylet const& root,
|
||||
uint256 const& after,
|
||||
std::uint64_t const hint,
|
||||
unsigned int limit,
|
||||
std::function<bool(std::shared_ptr<SLE const> const&)> const& f);
|
||||
|
||||
/** Iterate all items in an account's owner directory. */
|
||||
inline void
|
||||
forEachItem(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
std::function<void(std::shared_ptr<SLE const> const&)> const& f)
|
||||
{
|
||||
return forEachItem(view, keylet::ownerDir(id), f);
|
||||
}
|
||||
|
||||
/** Iterate all items after an item in an owner directory.
|
||||
@param after The key of the item to start after
|
||||
@param hint The directory page containing `after`
|
||||
@param limit The maximum number of items to return
|
||||
@return `false` if the iteration failed
|
||||
*/
|
||||
inline bool
|
||||
forEachItemAfter(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
uint256 const& after,
|
||||
std::uint64_t const hint,
|
||||
unsigned int limit,
|
||||
std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
|
||||
{
|
||||
return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f);
|
||||
}
|
||||
|
||||
/** Returns `true` if the directory is empty
|
||||
@param key The key of the directory
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
dirIsEmpty(ReadView const& view, Keylet const& k);
|
||||
|
||||
/** Returns a function that sets the owner on a directory SLE */
|
||||
[[nodiscard]] std::function<void(SLE::ref)>
|
||||
describeOwnerDir(AccountID const& account);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,160 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Transfer rate (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Returns MPT transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, MPTID const& issuanceID);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags = 0,
|
||||
std::optional<AccountID> holderID = std::nullopt);
|
||||
|
||||
/** Check if the account lacks required authorization for MPT.
|
||||
*
|
||||
* requireAuth check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy,
|
||||
int depth = 0);
|
||||
|
||||
/** Enforce account has MPToken to match its authorization.
|
||||
*
|
||||
* Called from doApply - it will check for expired (and delete if found any)
|
||||
* credentials matching DomainID set in MPTokenIssuance. Must be called if
|
||||
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal j);
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive MPT. Return tecNO_AUTH if it doesn't
|
||||
* and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Escrow operations (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
rippleLockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& uGrantorID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j);
|
||||
|
||||
TER
|
||||
rippleUnlockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& uGrantorID,
|
||||
AccountID const& uGranteeID,
|
||||
STAmount const& netAmount,
|
||||
STAmount const& grossAmount,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Delete an offer.
|
||||
|
||||
Requirements:
|
||||
The offer must exist.
|
||||
The caller must have already checked permissions.
|
||||
|
||||
@param view The ApplyView to modify.
|
||||
@param sle The offer to delete.
|
||||
@param j Journal for logging.
|
||||
|
||||
@return tesSUCCESS on success, otherwise an error code.
|
||||
*/
|
||||
// [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile.
|
||||
TER
|
||||
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,255 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// RippleState (Trustline) helpers
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Credit functions (from Credit.h)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Calculate the maximum amount of IOUs that an account can hold
|
||||
@param view the ledger to check against.
|
||||
@param account the account of interest.
|
||||
@param issuer the issuer of the IOU.
|
||||
@param currency the IOU to check.
|
||||
@return The maximum amount that can be held.
|
||||
*/
|
||||
/** @{ */
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency);
|
||||
|
||||
IOUAmount
|
||||
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur);
|
||||
/** @} */
|
||||
|
||||
/** Returns the amount of IOUs issued by issuer that are held by an account
|
||||
@param view the ledger to check against.
|
||||
@param account the account of interest.
|
||||
@param issuer the issuer of the IOU.
|
||||
@param currency the IOU to check.
|
||||
*/
|
||||
/** @{ */
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency);
|
||||
/** @} */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isIndividualFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
// Overload with depth parameter for uniformity with MPTIssue version.
|
||||
// The depth parameter is ignored for IOUs since they don't have vault recursion.
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
|
||||
{
|
||||
return isFrozen(view, account, issue);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
int = 0 /*ignored*/)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Trust line operations
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Create a trust line
|
||||
|
||||
This can set an initial balance.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // --> ripple state entry
|
||||
SLE::ref sleAccount, // --> the account being set.
|
||||
bool const bAuth, // --> authorize account.
|
||||
bool const bNoRipple, // --> others cannot ripple through
|
||||
bool const bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
trustDelete(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// IOU issuance/redemption
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
issueIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
redeemIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Check if the account lacks required authorization.
|
||||
*
|
||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||
* and tesSUCCESS otherwise.
|
||||
*
|
||||
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
|
||||
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
|
||||
* RippleState does exist, and the RippleState is not authorized.
|
||||
*
|
||||
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
|
||||
* RippleState exists, and is not authorized. Return tecNO_LINE if
|
||||
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
|
||||
* WeakAuth and lsfRequireAuth is *not* set, this function will return
|
||||
* tesSUCCESS even if RippleState does *not* exist.
|
||||
*
|
||||
* The default "Legacy" auth type is equivalent to WeakAuth.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
Issue const& issue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy);
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive IOU. Return terNO_RIPPLE if rippling is
|
||||
* disabled on both sides and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/// Any transactors that call addEmptyHolding() in doApply must call
|
||||
/// canAddHolding() in preflight with the same View and Asset
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
/** Delete trustline to AMM. The passed `sle` must be obtained from a prior
|
||||
* call to view.peek(). Fail if neither side of the trustline is AMM or
|
||||
* if ammAccountID is seated and is not one of the trustline's side.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
deleteAMMTrustLine(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleState,
|
||||
std::optional<AccountID> const& ammAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,286 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Enums for token handling
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Controls the treatment of frozen account balances */
|
||||
enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN };
|
||||
|
||||
/** Controls the treatment of unauthorized MPT balances */
|
||||
enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED };
|
||||
|
||||
/** Controls whether to include the account's full spendable balance */
|
||||
enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE };
|
||||
|
||||
enum class WaiveTransferFee : bool { No = false, Yes };
|
||||
|
||||
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
|
||||
* - StrongAuth - before checking if authorization is required
|
||||
* - WeakAuth
|
||||
* for MPT - after checking lsfMPTRequireAuth flag
|
||||
* for IOU - do not check if trust line exists
|
||||
* - Legacy
|
||||
* for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
|
||||
* for IOU - do not check if trust line exists i.e. same as WeakAuth
|
||||
*/
|
||||
enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Issue const& issue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Asset const& asset,
|
||||
int depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Account balance functions (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Returns the amount an account can spend.
|
||||
//
|
||||
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
|
||||
// without going into debt.
|
||||
//
|
||||
// If shFULL_BALANCE is specified, this is the amount the account can spend
|
||||
// total. Specifically:
|
||||
// * The account can go into debt if using a trust line, and the other side has
|
||||
// a non-zero limit.
|
||||
// * If the account is the asset issuer the limit is defined by the asset /
|
||||
// issuance.
|
||||
//
|
||||
// <-- saAmount: amount of currency held by account. May be negative.
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Asset const& asset,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
// Returns the amount an account can spend of the currency type saDefault, or
|
||||
// returns saDefault if this account is the issuer of the currency in
|
||||
// question. Should be used in favor of accountHolds when questioning how much
|
||||
// an account can spend while also allowing currency issuers to spend
|
||||
// unlimited amounts of their own currency (since they can always issue more).
|
||||
[[nodiscard]] STAmount
|
||||
accountFunds(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j);
|
||||
|
||||
/** Returns the transfer fee as Rate based on the type of token
|
||||
* @param view The ledger view
|
||||
* @param amount The amount to transfer
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding operations (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy);
|
||||
|
||||
[[nodiscard]] TER
|
||||
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Money Transfers (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Direct send w/o fees:
|
||||
// - Redeeming IOUs and/or sending sender's own IOUs.
|
||||
// - Create trust line of needed.
|
||||
// --> bCheckIssuer : normally require issuer to be involved.
|
||||
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
|
||||
|
||||
/** Calls static rippleCreditIOU if saAmount represents Issue.
|
||||
* Calls static rippleCreditMPT if saAmount represents MPTIssue.
|
||||
*/
|
||||
TER
|
||||
rippleCredit(
|
||||
ApplyView& view,
|
||||
AccountID const& uSenderID,
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
bool bCheckIssuer,
|
||||
beast::Journal j);
|
||||
|
||||
/** Calls static accountSendIOU if saAmount represents Issue.
|
||||
* Calls static accountSendMPT if saAmount represents MPTIssue.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
accountSend(
|
||||
ApplyView& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
WaiveTransferFee waiveFee = WaiveTransferFee::No);
|
||||
|
||||
using MultiplePaymentDestinations = std::vector<std::pair<AccountID, Number>>;
|
||||
/** Like accountSend, except one account is sending multiple payments (with the
|
||||
* same asset!) simultaneously
|
||||
*
|
||||
* Calls static accountSendMultiIOU if saAmount represents Issue.
|
||||
* Calls static accountSendMultiMPT if saAmount represents MPTIssue.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
accountSendMulti(
|
||||
ApplyView& view,
|
||||
AccountID const& senderID,
|
||||
Asset const& asset,
|
||||
MultiplePaymentDestinations const& receivers,
|
||||
beast::Journal j,
|
||||
WaiveTransferFee waiveFee = WaiveTransferFee::No);
|
||||
|
||||
[[nodiscard]] TER
|
||||
transferXRP(
|
||||
ApplyView& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** From the perspective of a vault, return the number of shares to give
|
||||
depositor when they offer a fixed amount of assets. Note, since shares are
|
||||
MPT, this number is integral and always truncated in this calculation.
|
||||
|
||||
@param vault The vault SLE.
|
||||
@param issuance The MPTokenIssuance SLE for the vault's shares.
|
||||
@param assets The amount of assets to convert.
|
||||
|
||||
@return The number of shares, or nullopt on error.
|
||||
*/
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets);
|
||||
|
||||
/** From the perspective of a vault, return the number of assets to take from
|
||||
depositor when they receive a fixed amount of shares. Note, since shares are
|
||||
MPT, they are always an integral number.
|
||||
|
||||
@param vault The vault SLE.
|
||||
@param issuance The MPTokenIssuance SLE for the vault's shares.
|
||||
@param shares The amount of shares to convert.
|
||||
|
||||
@return The number of assets, or nullopt on error.
|
||||
*/
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares);
|
||||
|
||||
/** Controls whether to truncate shares instead of rounding. */
|
||||
enum class TruncateShares : bool { no = false, yes = true };
|
||||
|
||||
/** From the perspective of a vault, return the number of shares to demand from
|
||||
the depositor when they ask to withdraw a fixed amount of assets. Since
|
||||
shares are MPT this number is integral, and it will be rounded to nearest
|
||||
unless explicitly requested to be truncated instead.
|
||||
|
||||
@param vault The vault SLE.
|
||||
@param issuance The MPTokenIssuance SLE for the vault's shares.
|
||||
@param assets The amount of assets to convert.
|
||||
@param truncate Whether to truncate instead of rounding.
|
||||
|
||||
@return The number of shares, or nullopt on error.
|
||||
*/
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets,
|
||||
TruncateShares truncate = TruncateShares::no);
|
||||
|
||||
/** From the perspective of a vault, return the number of assets to give the
|
||||
depositor when they redeem a fixed amount of shares. Note, since shares are
|
||||
MPT, they are always an integral number.
|
||||
|
||||
@param vault The vault SLE.
|
||||
@param issuance The MPTokenIssuance SLE for the vault's shares.
|
||||
@param shares The amount of shares to convert.
|
||||
|
||||
@return The number of assets, or nullopt on error.
|
||||
*/
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -138,6 +138,22 @@ public:
|
||||
/** Returns the number of file descriptors the backend expects to need. */
|
||||
virtual int
|
||||
fdRequired() const = 0;
|
||||
|
||||
/** The number of hardware threads to use for compression of a batch. */
|
||||
static unsigned int const numHardwareThreads;
|
||||
|
||||
/** Calculate parallelization parameters for a batch of items.
|
||||
|
||||
Determines the number of threads and items per thread needed for parallel batch processing.
|
||||
|
||||
@param batchSize Number of items to process
|
||||
@param maxThreadCount Maximum number of threads to use.
|
||||
@return A pair of (numThreads, numItems) where numThreads is the exact number of threads to
|
||||
use, and numItems is the number of items per thread. The last thread may process
|
||||
fewer items.
|
||||
*/
|
||||
static std::pair<unsigned int, unsigned int>
|
||||
calculateBatchParallelism(unsigned int batchSize, unsigned int maxThreadCount);
|
||||
};
|
||||
|
||||
} // namespace NodeStore
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
# Linux Packaging
|
||||
|
||||
This directory contains all files needed to build RPM and Debian packages for `xrpld`.
|
||||
|
||||
## Directory layout
|
||||
|
||||
```
|
||||
package/
|
||||
build-pkg.sh Script called by CMake targets to drive the build tool
|
||||
rpm/
|
||||
xrpld.spec.in RPM spec template (CMake substitutes @xrpld_version@)
|
||||
deb/
|
||||
debian/ Debian control files (control, rules, install, links, logrotate)
|
||||
shared/
|
||||
xrpld.service systemd unit file (used by both RPM and DEB)
|
||||
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
|
||||
xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Both package types require a pre-built `xrpld` binary in the CMake build directory, produced by a prior `cmake --build` step with `-Dxrpld=ON`.
|
||||
|
||||
| Package type | Build container | Tool required |
|
||||
| ------------ | ----------------------------------------- | -------------------------------------------------------------- |
|
||||
| RPM | `ghcr.io/xrplf/ci/rhel-9:gcc-12-*` | `rpmbuild` |
|
||||
| DEB | `ghcr.io/xrplf/ci/debian-bookworm:gcc-12` | `dpkg-buildpackage`, `debhelper (= 13)`, `dh-sequence-systemd` |
|
||||
|
||||
## Building packages via CMake
|
||||
|
||||
Configure with the required install prefix, then invoke the target:
|
||||
|
||||
```bash
|
||||
cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=/opt/xrpld \
|
||||
-Dxrpld=ON \
|
||||
-Dtests=OFF \
|
||||
..
|
||||
|
||||
# RPM (in RHEL container):
|
||||
cmake --build . --target package-rpm
|
||||
|
||||
# DEB (in Debian container):
|
||||
cmake --build . --target package-deb
|
||||
```
|
||||
|
||||
The `cmake/XrplPackaging.cmake` module gates each target on whether the required
|
||||
tool (`rpmbuild` / `dpkg-buildpackage`) is present at configure time, so
|
||||
configuring on a host that lacks one simply omits the corresponding target.
|
||||
|
||||
`CMAKE_INSTALL_PREFIX` must be `/opt/xrpld`; if it is not, both targets are
|
||||
skipped with a `STATUS` message.
|
||||
|
||||
## How `build-pkg.sh` works
|
||||
|
||||
`build-pkg.sh <pkg_type> <src_dir> <build_dir> [version]` is invoked by the
|
||||
CMake custom targets. CMake passes `CMAKE_SOURCE_DIR`, `CMAKE_BINARY_DIR`, and
|
||||
(for DEB) `xrpld_version`.
|
||||
|
||||
### RPM
|
||||
|
||||
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
|
||||
2. Copies the generated `xrpld.spec` (from `CMAKE_BINARY_DIR/package/rpm/`) and the shared support files into `SPECS/` and `SOURCES/`.
|
||||
3. Runs `rpmbuild -bb` with `cmake_build_dir` defined so the spec can call `cmake --install`.
|
||||
4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
|
||||
|
||||
### DEB
|
||||
|
||||
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
|
||||
2. Copies `README.md` and `LICENSE.md` (referenced by `debian/rules`).
|
||||
3. Copies `package/deb/debian/` control files into `debbuild/source/debian/`.
|
||||
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
|
||||
5. Generates a minimal `debian/changelog` (pre-release versions use `~` instead of `-`).
|
||||
6. Runs `dpkg-buildpackage -b --no-sign` with `CMAKE_BUILD_DIR` set so `debian/rules` can call `cmake --install`.
|
||||
7. Output: `debbuild/*.deb` (one level above the staging source root)
|
||||
|
||||
## Post-build verification
|
||||
|
||||
```bash
|
||||
# DEB
|
||||
dpkg-deb -c debbuild/*.deb | grep -E 'systemd|sysusers|tmpfiles'
|
||||
lintian -I debbuild/*.deb
|
||||
|
||||
# RPM
|
||||
rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
## Reproducibility
|
||||
|
||||
The following environment variables improve build reproducibility. They are not
|
||||
set automatically by `build-pkg.sh`; set them manually if needed:
|
||||
|
||||
```bash
|
||||
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
export TZ=UTC
|
||||
export LC_ALL=C.UTF-8
|
||||
export GZIP=-n
|
||||
export DEB_BUILD_OPTIONS="noautodbgsym reproducible=+fixfilepath"
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- Port debsigs signing instructions and integrate into CI.
|
||||
- Port RPM GPG signing setup (key import + `%{?_gpg_sign}` in spec).
|
||||
- Introduce a virtual package for key rotation.
|
||||
|
||||
## Historical notes
|
||||
|
||||
`ci/packaging/package_checklist.sh` and `ci/packaging/final_packaging_improvements.md`
|
||||
were development scratch files used during initial prototyping. They are
|
||||
superseded by this README, `build-pkg.sh`, and the reusable CI workflows
|
||||
(`reusable-package-rpm.yml`, `reusable-package-deb.yml`).
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build an RPM or Debian package from a pre-built xrpld binary.
|
||||
#
|
||||
# Usage: build-pkg.sh <pkg_type> <src_dir> <build_dir> [version]
|
||||
# pkg_type : rpm | deb
|
||||
# src_dir : path to repository root (CMAKE_SOURCE_DIR)
|
||||
# build_dir : path to CMake build directory (CMAKE_BINARY_DIR)
|
||||
# version : package version string (required for deb; e.g. 2.4.0-b1)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PKG_TYPE="${1:?pkg_type required}"
|
||||
SRC_DIR="${2:?src_dir required}"
|
||||
BUILD_DIR="${3:?build_dir required}"
|
||||
|
||||
case "${PKG_TYPE}" in
|
||||
rpm)
|
||||
set -x
|
||||
mkdir -p \
|
||||
"${BUILD_DIR}/rpmbuild/BUILD" \
|
||||
"${BUILD_DIR}/rpmbuild/BUILDROOT" \
|
||||
"${BUILD_DIR}/rpmbuild/RPMS" \
|
||||
"${BUILD_DIR}/rpmbuild/SOURCES" \
|
||||
"${BUILD_DIR}/rpmbuild/SPECS" \
|
||||
"${BUILD_DIR}/rpmbuild/SRPMS"
|
||||
|
||||
cp "${BUILD_DIR}/package/rpm/xrpld.spec" \
|
||||
"${BUILD_DIR}/rpmbuild/SPECS/xrpld.spec"
|
||||
|
||||
cp "${SRC_DIR}/package/shared/xrpld.service" \
|
||||
"${BUILD_DIR}/rpmbuild/SOURCES/xrpld.service"
|
||||
cp "${SRC_DIR}/package/shared/xrpld.sysusers" \
|
||||
"${BUILD_DIR}/rpmbuild/SOURCES/xrpld.sysusers"
|
||||
cp "${SRC_DIR}/package/shared/xrpld.tmpfiles" \
|
||||
"${BUILD_DIR}/rpmbuild/SOURCES/xrpld.tmpfiles"
|
||||
cp "${SRC_DIR}/package/shared/xrpld.logrotate" \
|
||||
"${BUILD_DIR}/rpmbuild/SOURCES/xrpld.logrotate"
|
||||
|
||||
rpmbuild -bb \
|
||||
--define "_topdir ${BUILD_DIR}/rpmbuild" \
|
||||
--define "cmake_build_dir ${BUILD_DIR}" \
|
||||
"${BUILD_DIR}/rpmbuild/SPECS/xrpld.spec"
|
||||
;;
|
||||
|
||||
deb)
|
||||
VERSION="${4:-1.0.0}"
|
||||
STAGING="${BUILD_DIR}/debbuild/source"
|
||||
|
||||
rm -rf "${STAGING}"
|
||||
mkdir -p "${STAGING}"
|
||||
|
||||
# Source files referenced by debian/rules
|
||||
cp "${SRC_DIR}/README.md" "${STAGING}/"
|
||||
cp "${SRC_DIR}/LICENSE.md" "${STAGING}/"
|
||||
|
||||
# debian/ control files
|
||||
cp -r "${SRC_DIR}/package/deb/debian" "${STAGING}/debian"
|
||||
|
||||
# Shared support files for dh_installsystemd/sysusers/tmpfiles
|
||||
cp "${SRC_DIR}/package/shared/xrpld.service" "${STAGING}/debian/xrpld.service"
|
||||
cp "${SRC_DIR}/package/shared/xrpld.sysusers" "${STAGING}/debian/xrpld.sysusers"
|
||||
cp "${SRC_DIR}/package/shared/xrpld.tmpfiles" "${STAGING}/debian/xrpld.tmpfiles"
|
||||
cp "${SRC_DIR}/package/shared/xrpld.logrotate" "${STAGING}/xrpld.logrotate"
|
||||
|
||||
# debian/changelog is required by dpkg-buildpackage; generate a minimal one.
|
||||
# Pre-release versions use ~ instead of - (e.g. 2.4.0-b1 → 2.4.0~b1).
|
||||
DEB_VERSION="${VERSION//-/\~}"
|
||||
cat > "${STAGING}/debian/changelog" <<EOF
|
||||
xrpld (${DEB_VERSION}-1) unstable; urgency=medium
|
||||
|
||||
* Release ${VERSION}.
|
||||
|
||||
-- XRPL Foundation <contact@xrpl.org> $(LC_ALL=C date -u -R)
|
||||
EOF
|
||||
|
||||
chmod +x "${STAGING}/debian/rules"
|
||||
|
||||
set -x
|
||||
cd "${STAGING}"
|
||||
# -d skips build-dep checks — we're packaging a pre-built binary, not building from source.
|
||||
CMAKE_BUILD_DIR="${BUILD_DIR}" dpkg-buildpackage -b --no-sign -d
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown package type: ${PKG_TYPE}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,33 +0,0 @@
|
||||
Source: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: XRPL Foundation <contact@xrpl.org>
|
||||
Rules-Requires-Root: no
|
||||
Build-Depends:
|
||||
debhelper-compat (= 13),
|
||||
Standards-Version: 4.7.0
|
||||
Homepage: https://github.com/XRPLF/rippled
|
||||
Vcs-Git: https://github.com/XRPLF/rippled.git
|
||||
Vcs-Browser: https://github.com/XRPLF/rippled
|
||||
|
||||
Package: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: any
|
||||
Depends:
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: XRP Ledger daemon
|
||||
xrpld is the reference implementation of the XRP Ledger protocol.
|
||||
It participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
|
||||
Package: rippled
|
||||
Architecture: all
|
||||
Section: oldlibs
|
||||
Priority: optional
|
||||
Depends: xrpld, ${misc:Depends}
|
||||
Description: transitional package - use xrpld
|
||||
The rippled package has been renamed to xrpld. This transitional
|
||||
package ensures a smooth upgrade and can be safely removed after
|
||||
xrpld is installed.
|
||||
@@ -1,20 +0,0 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: rippled
|
||||
Source: https://github.com/XRPLF/rippled
|
||||
|
||||
Files: *
|
||||
Copyright: 2012-2025 Ripple Labs Inc.
|
||||
License: ISC
|
||||
|
||||
License: ISC
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||
@:
|
||||
|
||||
override_dh_auto_install:
|
||||
DESTDIR=debian/tmp cmake --install $(CMAKE_BUILD_DIR) --component runtime
|
||||
install -Dm0644 xrpld.logrotate debian/tmp/opt/xrpld/bin/xrpld.logrotate
|
||||
install -Dm0644 README.md debian/tmp/usr/share/doc/xrpld/README.md
|
||||
install -Dm0644 LICENSE.md debian/tmp/usr/share/doc/xrpld/LICENSE.md
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd
|
||||
|
||||
override_dh_installsysusers:
|
||||
dh_installsysusers
|
||||
|
||||
override_dh_installtmpfiles:
|
||||
dh_installtmpfiles
|
||||
|
||||
override_dh_install:
|
||||
dh_install
|
||||
|
||||
override_dh_dwz:
|
||||
@:
|
||||
|
||||
override_dh_builddeb:
|
||||
dh_builddeb -- -Zgzip -z1
|
||||
@@ -1 +0,0 @@
|
||||
3.0 (quilt)
|
||||
@@ -1,2 +0,0 @@
|
||||
/opt/xrpld/etc/xrpld/xrpld.cfg
|
||||
/opt/xrpld/etc/xrpld/validators.txt
|
||||
@@ -1,8 +0,0 @@
|
||||
opt/xrpld/bin/xrpld
|
||||
opt/xrpld/bin/xrpld.logrotate
|
||||
|
||||
opt/xrpld/etc/xrpld/xrpld.cfg
|
||||
opt/xrpld/etc/xrpld/validators.txt
|
||||
|
||||
usr/share/doc/xrpld/README.md
|
||||
usr/share/doc/xrpld/LICENSE.md
|
||||
@@ -1,13 +0,0 @@
|
||||
opt/xrpld/etc etc/opt/xrpld
|
||||
|
||||
opt/xrpld/bin/xrpld usr/bin/xrpld
|
||||
|
||||
## remove when "rippled" deprecated
|
||||
opt/xrpld/bin/xrpld opt/xrpld/bin/rippled
|
||||
opt/xrpld/bin/xrpld usr/bin/rippled
|
||||
opt/xrpld/bin/xrpld usr/local/bin/rippled
|
||||
opt/xrpld/etc/xrpld/xrpld.cfg opt/xrpld/etc/xrpld/rippled.cfg
|
||||
var/log/xrpld var/log/rippled
|
||||
var/lib/xrpld var/lib/rippled
|
||||
opt/xrpld opt/ripple
|
||||
etc/opt/xrpld etc/opt/ripple
|
||||
@@ -1,76 +0,0 @@
|
||||
%global xrpld_version @xrpld_version@
|
||||
%global _opt_prefix /opt/xrpld
|
||||
%global ver_base %(v=%{xrpld_version}; echo ${v%%-*})
|
||||
%global _has_dash %(v=%{xrpld_version}; [ "${v#*-}" != "$v" ] && echo 1 || echo 0)
|
||||
%if 0%{?_has_dash}
|
||||
%global ver_suffix %(v=%{xrpld_version}; printf %s "${v#*-}")
|
||||
%endif
|
||||
|
||||
Name: xrpld
|
||||
Version: %{ver_base}
|
||||
Release: %{?ver_suffix:0.%{ver_suffix}}%{!?ver_suffix:1}%{?dist}
|
||||
Summary: XRP Ledger daemon
|
||||
|
||||
License: ISC
|
||||
URL: https://github.com/XRPLF/rippled
|
||||
|
||||
Source0: xrpld.service
|
||||
Source1: xrpld.sysusers
|
||||
Source2: xrpld.tmpfiles
|
||||
Source3: xrpld.logrotate
|
||||
|
||||
BuildArch: x86_64
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
%{?systemd_requires}
|
||||
%{?sysusers_requires_compat}
|
||||
|
||||
%description
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
|
||||
%install
|
||||
rm -rf %{buildroot}
|
||||
|
||||
# Install binary and config files via CMake component install.
|
||||
# Requires cmake was configured with -DCMAKE_INSTALL_PREFIX=/opt/xrpld.
|
||||
DESTDIR=%{buildroot} cmake --install %{cmake_build_dir} --component runtime
|
||||
|
||||
# Create the rippled compatibility symlink alongside the binary.
|
||||
ln -s xrpld %{buildroot}%{_opt_prefix}/bin/rippled
|
||||
|
||||
# Install systemd/sysusers/tmpfiles support files.
|
||||
install -Dm0644 %{SOURCE0} %{buildroot}%{_unitdir}/xrpld.service
|
||||
install -Dm0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/xrpld.conf
|
||||
install -Dm0644 %{SOURCE2} %{buildroot}%{_tmpfilesdir}/xrpld.conf
|
||||
install -Dm0644 %{SOURCE3} %{buildroot}%{_opt_prefix}/bin/xrpld.logrotate
|
||||
|
||||
%pre
|
||||
%sysusers_create_compat %{SOURCE1}
|
||||
|
||||
%post
|
||||
%systemd_post xrpld.service
|
||||
|
||||
%preun
|
||||
%systemd_preun xrpld.service
|
||||
|
||||
%postun
|
||||
%systemd_postun_with_restart xrpld.service
|
||||
|
||||
%files
|
||||
%dir %{_opt_prefix}
|
||||
%dir %{_opt_prefix}/bin
|
||||
%{_opt_prefix}/bin/xrpld
|
||||
%{_opt_prefix}/bin/xrpld.logrotate
|
||||
%{_opt_prefix}/bin/rippled
|
||||
%dir %{_opt_prefix}/etc
|
||||
%dir %{_opt_prefix}/etc/xrpld
|
||||
%config(noreplace) %{_opt_prefix}/etc/xrpld/xrpld.cfg
|
||||
%config(noreplace) %{_opt_prefix}/etc/xrpld/validators.txt
|
||||
%{_unitdir}/xrpld.service
|
||||
%{_sysusersdir}/xrpld.conf
|
||||
%{_tmpfilesdir}/xrpld.conf
|
||||
%ghost %dir /var/opt/ripple
|
||||
%ghost %dir /var/opt/ripple/lib
|
||||
%ghost %dir /var/opt/ripple/log
|
||||
@@ -1,15 +0,0 @@
|
||||
/var/log/xrpld/*.log {
|
||||
daily
|
||||
minsize 200M
|
||||
rotate 7
|
||||
nocreate
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
compresscmd /usr/bin/nice
|
||||
compressoptions -n19 ionice -c3 gzip
|
||||
compressext .gz
|
||||
postrotate
|
||||
/opt/xrpld/bin/xrpld --conf /opt/xrpld/etc/xrpld/xrpld.cfg logrotate
|
||||
endscript
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[Unit]
|
||||
Description=XRP Ledger Daemon
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/xrpld/bin/xrpld --net --silent --conf /opt/xrpld/etc/xrpld/xrpld.cfg
|
||||
Restart=on-failure
|
||||
User=xrpld
|
||||
Group=xrpld
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1 +0,0 @@
|
||||
u xrpld - "XRP Ledger daemon" /var/lib/xrpld /sbin/nologin
|
||||
@@ -1,2 +0,0 @@
|
||||
d /var/opt/ripple/lib 0750 xrpld xrpld -
|
||||
d /var/opt/ripple/log 0750 xrpld xrpld -
|
||||
@@ -1,7 +1,5 @@
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
//
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
60
src/libxrpl/ledger/Credit.cpp
Normal file
60
src/libxrpl/ledger/Credit.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit);
|
||||
result.setIssuer(account);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match");
|
||||
return result;
|
||||
}
|
||||
|
||||
IOUAmount
|
||||
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur)
|
||||
{
|
||||
return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
|
||||
}
|
||||
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
result = sleRippleState->getFieldAmount(sfBalance);
|
||||
if (account < issuer)
|
||||
result.negate();
|
||||
result.setIssuer(account);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match");
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,247 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer)
|
||||
{
|
||||
if (isXRP(issuer))
|
||||
return false;
|
||||
if (auto const sle = view.read(keylet::account(issuer)))
|
||||
return sle->isFlag(lsfGlobalFreeze);
|
||||
return false;
|
||||
}
|
||||
|
||||
// An owner count cannot be negative. If adjustment would cause a negative
|
||||
// owner count, clamp the owner count at 0. Similarly for overflow. This
|
||||
// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
|
||||
// If id != std::nullopt, then do error reporting.
|
||||
//
|
||||
// Returns adjusted owner count.
|
||||
static std::uint32_t
|
||||
confineOwnerCount(
|
||||
std::uint32_t current,
|
||||
std::int32_t adjustment,
|
||||
std::optional<AccountID> const& id = std::nullopt,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
{
|
||||
std::uint32_t adjusted{current + adjustment};
|
||||
if (adjustment > 0)
|
||||
{
|
||||
// Overflow is well defined on unsigned
|
||||
if (adjusted < current)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!";
|
||||
}
|
||||
adjusted = std::numeric_limits<std::uint32_t>::max();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Underflow is well defined on unsigned
|
||||
if (adjusted > current)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!";
|
||||
}
|
||||
adjusted = 0;
|
||||
XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
|
||||
}
|
||||
}
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
|
||||
{
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
if (sle == nullptr)
|
||||
return beast::zero;
|
||||
|
||||
// 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);
|
||||
|
||||
auto const fullBalance = sle->getFieldAmount(sfBalance);
|
||||
|
||||
auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
|
||||
|
||||
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;
|
||||
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
|
||||
<< " amount=" << amount.getFullText()
|
||||
<< " fullBalance=" << fullBalance.getFullText()
|
||||
<< " balance=" << balance.getFullText() << " reserve=" << reserve
|
||||
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
|
||||
|
||||
return amount.xrp();
|
||||
}
|
||||
|
||||
Rate
|
||||
transferRate(ReadView const& view, AccountID const& issuer)
|
||||
{
|
||||
auto const sle = view.read(keylet::account(issuer));
|
||||
|
||||
if (sle && sle->isFieldPresent(sfTransferRate))
|
||||
return Rate{sle->getFieldU32(sfTransferRate)};
|
||||
|
||||
return parityRate;
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCount(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
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;
|
||||
view.update(sle);
|
||||
}
|
||||
|
||||
AccountID
|
||||
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
|
||||
{
|
||||
// This number must not be changed without an amendment
|
||||
constexpr std::uint16_t maxAccountAttempts = 256;
|
||||
for (std::uint16_t i = 0; i < maxAccountAttempts; ++i)
|
||||
{
|
||||
ripesha_hasher rsh;
|
||||
auto const hash = sha512Half(i, view.header().parentHash, pseudoOwnerKey);
|
||||
rsh(hash.data(), hash.size());
|
||||
AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
|
||||
if (!view.read(keylet::account(ret)))
|
||||
return ret;
|
||||
}
|
||||
return beast::zero;
|
||||
}
|
||||
|
||||
// Pseudo-account designator fields MUST be maintained by including the
|
||||
// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
|
||||
// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
|
||||
// since a non-active amendment will not set any field, by definition.
|
||||
// Specific properties of a pseudo-account are NOT checked here, that's what
|
||||
// InvariantCheck is for.
|
||||
[[nodiscard]] std::vector<SField const*> const&
|
||||
getPseudoAccountFields()
|
||||
{
|
||||
static std::vector<SField const*> const pseudoFields = []() {
|
||||
auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT);
|
||||
if (!ar)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
LogicError(
|
||||
"xrpl::getPseudoAccountFields : unable to find account root "
|
||||
"ledger format");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto const& soTemplate = ar->getSOTemplate();
|
||||
|
||||
std::vector<SField const*> pseudoFields;
|
||||
for (auto const& field : soTemplate)
|
||||
{
|
||||
if (field.sField().shouldMeta(SField::sMD_PseudoAccount))
|
||||
pseudoFields.emplace_back(&field.sField());
|
||||
}
|
||||
return pseudoFields;
|
||||
}();
|
||||
return pseudoFields;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(
|
||||
std::shared_ptr<SLE const> sleAcct,
|
||||
std::set<SField const*> const& pseudoFieldFilter)
|
||||
{
|
||||
auto const& fields = getPseudoAccountFields();
|
||||
|
||||
// Intentionally use defensive coding here because it's cheap and makes the
|
||||
// semantics of true return value clean.
|
||||
return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
|
||||
std::count_if(
|
||||
fields.begin(), fields.end(), [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
|
||||
return sleAcct->isFieldPresent(*sf) &&
|
||||
(pseudoFieldFilter.empty() || pseudoFieldFilter.contains(sf));
|
||||
}) > 0;
|
||||
}
|
||||
|
||||
Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField)
|
||||
{
|
||||
[[maybe_unused]]
|
||||
auto const& fields = getPseudoAccountFields();
|
||||
XRPL_ASSERT(
|
||||
std::count_if(
|
||||
fields.begin(),
|
||||
fields.end(),
|
||||
[&ownerField](SField const* sf) -> bool { return *sf == ownerField; }) == 1,
|
||||
"xrpl::createPseudoAccount : valid owner field");
|
||||
|
||||
auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey);
|
||||
if (accountId == beast::zero)
|
||||
return Unexpected(tecDUPLICATE);
|
||||
|
||||
// Create pseudo-account.
|
||||
auto account = std::make_shared<SLE>(keylet::account(accountId));
|
||||
account->setAccountID(sfAccount, accountId);
|
||||
account->setFieldAmount(sfBalance, STAmount{});
|
||||
|
||||
// Pseudo-accounts can't submit transactions, so set the sequence number
|
||||
// to 0 to make them easier to spot and verify, and add an extra level
|
||||
// of protection.
|
||||
std::uint32_t const seqno = //
|
||||
view.rules().enabled(featureSingleAssetVault) || //
|
||||
view.rules().enabled(featureLendingProtocol) //
|
||||
? 0 //
|
||||
: view.seq();
|
||||
account->setFieldU32(sfSequence, seqno);
|
||||
// Ignore reserves requirement, disable the master key, allow default
|
||||
// rippling, and enable deposit authorization to prevent payments into
|
||||
// pseudo-account.
|
||||
account->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
// Link the pseudo-account with its owner object.
|
||||
account->setFieldH256(ownerField, pseudoOwnerKey);
|
||||
|
||||
view.insert(account);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
|
||||
{
|
||||
if (toSle == nullptr)
|
||||
return tecNO_DST;
|
||||
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
|
||||
return tecDST_TAG_NEEDED; // Cannot send without a tag
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,177 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
//
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
dirFirst(
|
||||
ApplyView& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry)
|
||||
{
|
||||
return detail::internalDirFirst(view, root, page, index, entry);
|
||||
}
|
||||
|
||||
bool
|
||||
dirNext(
|
||||
ApplyView& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry)
|
||||
{
|
||||
return detail::internalDirNext(view, root, page, index, entry);
|
||||
}
|
||||
|
||||
bool
|
||||
cdirFirst(
|
||||
ReadView const& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE const>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry)
|
||||
{
|
||||
return detail::internalDirFirst(view, root, page, index, entry);
|
||||
}
|
||||
|
||||
bool
|
||||
cdirNext(
|
||||
ReadView const& view,
|
||||
uint256 const& root,
|
||||
std::shared_ptr<SLE const>& page,
|
||||
unsigned int& index,
|
||||
uint256& entry)
|
||||
{
|
||||
return detail::internalDirNext(view, root, page, index, entry);
|
||||
}
|
||||
|
||||
void
|
||||
forEachItem(
|
||||
ReadView const& view,
|
||||
Keylet const& root,
|
||||
std::function<void(std::shared_ptr<SLE const> const&)> const& f)
|
||||
{
|
||||
XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItem : valid root type");
|
||||
|
||||
if (root.type != ltDIR_NODE)
|
||||
return;
|
||||
|
||||
auto pos = root;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto sle = view.read(pos);
|
||||
if (!sle)
|
||||
return;
|
||||
for (auto const& key : sle->getFieldV256(sfIndexes))
|
||||
f(view.read(keylet::child(key)));
|
||||
auto const next = sle->getFieldU64(sfIndexNext);
|
||||
if (!next)
|
||||
return;
|
||||
pos = keylet::page(root, next);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
forEachItemAfter(
|
||||
ReadView const& view,
|
||||
Keylet const& root,
|
||||
uint256 const& after,
|
||||
std::uint64_t const hint,
|
||||
unsigned int limit,
|
||||
std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
|
||||
{
|
||||
XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItemAfter : valid root type");
|
||||
|
||||
if (root.type != ltDIR_NODE)
|
||||
return false;
|
||||
|
||||
auto currentIndex = root;
|
||||
|
||||
// If startAfter is not zero try jumping to that page using the hint
|
||||
if (after.isNonZero())
|
||||
{
|
||||
auto const hintIndex = keylet::page(root, hint);
|
||||
|
||||
if (auto hintDir = view.read(hintIndex))
|
||||
{
|
||||
for (auto const& key : hintDir->getFieldV256(sfIndexes))
|
||||
{
|
||||
if (key == after)
|
||||
{
|
||||
// We found the hint, we can start here
|
||||
currentIndex = hintIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (;;)
|
||||
{
|
||||
auto const ownerDir = view.read(currentIndex);
|
||||
if (!ownerDir)
|
||||
return found;
|
||||
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
|
||||
{
|
||||
if (!found)
|
||||
{
|
||||
if (key == after)
|
||||
found = true;
|
||||
}
|
||||
else if (f(view.read(keylet::child(key))) && limit-- <= 1)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
return found;
|
||||
currentIndex = keylet::page(root, uNodeNext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
auto const ownerDir = view.read(currentIndex);
|
||||
if (!ownerDir)
|
||||
return true;
|
||||
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
|
||||
{
|
||||
if (f(view.read(keylet::child(key))) && limit-- <= 1)
|
||||
return true;
|
||||
}
|
||||
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
return true;
|
||||
currentIndex = keylet::page(root, uNodeNext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
dirIsEmpty(ReadView const& view, Keylet const& k)
|
||||
{
|
||||
auto const sleNode = view.read(k);
|
||||
if (!sleNode)
|
||||
return true;
|
||||
if (!sleNode->getFieldV256(sfIndexes).empty())
|
||||
return false;
|
||||
// The first page of a directory may legitimately be empty even if there
|
||||
// are other pages (the first page is the anchor page) so check to see if
|
||||
// there is another page. If there is, the directory isn't empty.
|
||||
return sleNode->getFieldU64(sfIndexNext) == 0;
|
||||
}
|
||||
|
||||
std::function<void(SLE::ref)>
|
||||
describeOwnerDir(AccountID const& account)
|
||||
{
|
||||
return [account](std::shared_ptr<SLE> const& sle) { (*sle)[sfOwner] = account; };
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,766 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Forward declarations for functions that remain in View.h/cpp
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
int depth);
|
||||
|
||||
[[nodiscard]] TER
|
||||
dirLink(
|
||||
ApplyView& view,
|
||||
AccountID const& owner,
|
||||
std::shared_ptr<SLE>& object,
|
||||
SF_UINT64 const& node = sfOwnerNode);
|
||||
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
|
||||
{
|
||||
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
|
||||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth)
|
||||
{
|
||||
if (isGlobalFrozen(view, mptIssue))
|
||||
return true;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isIndividualFrozen(view, account, mptIssue))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Rate
|
||||
transferRate(ReadView const& view, MPTID const& issuanceID)
|
||||
{
|
||||
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
|
||||
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
|
||||
// which represents 50% of 1,000,000,000
|
||||
if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
|
||||
sle && sle->isFieldPresent(sfTransferFee))
|
||||
return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
|
||||
|
||||
return parityRate;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
|
||||
{
|
||||
auto mptID = mptIssue.getMptID();
|
||||
auto issuance = view.read(keylet::mptIssuance(mptID));
|
||||
if (!issuance)
|
||||
{
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
}
|
||||
if (!issuance->isFlag(lsfMPTCanTransfer))
|
||||
{
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mpt = view.peek(keylet::mptIssuance(mptID));
|
||||
if (!mpt)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (mpt->isFlag(lsfMPTLocked))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (view.peek(keylet::mptoken(mptID, accountID)))
|
||||
return tecDUPLICATE;
|
||||
if (accountID == mptIssue.getIssuer())
|
||||
return tesSUCCESS;
|
||||
|
||||
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags,
|
||||
std::optional<AccountID> holderID)
|
||||
{
|
||||
auto const sleAcct = view.peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted the tx is a holder
|
||||
// Note: `account_` is holder's account
|
||||
// `holderID` is NOT used
|
||||
if (!holderID)
|
||||
{
|
||||
// When a holder wants to unauthorize/delete a MPT, the ledger must
|
||||
// - delete mptokenKey from owner directory
|
||||
// - delete the MPToken
|
||||
if (flags & tfMPTUnauthorize)
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const sleMpt = view.peek(mptokenKey);
|
||||
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleAcct, -1, journal);
|
||||
|
||||
view.erase(sleMpt);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// A potential holder wants to authorize/hold a mpt, the ledger must:
|
||||
// - add the new mptokenKey to the owner directory
|
||||
// - create the MPToken object for the holder
|
||||
|
||||
// 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::zero)
|
||||
: view.fees().accountReserve(uOwnerCount + 1));
|
||||
|
||||
if (priorBalance < reserveCreate)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Defensive check before we attempt to create MPToken for the issuer
|
||||
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!mpt || mpt->getAccountID(sfIssuer) == account)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
|
||||
if (view.rules().enabled(featureLendingProtocol))
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
if (auto ter = dirLink(view, account, mptoken))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
(*mptoken)[sfFlags] = 0;
|
||||
view.insert(mptoken);
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, sleAcct, 1, journal);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleMptIssuance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted this tx is the issuer of the MPT
|
||||
// Note: `account_` is issuer's account
|
||||
// `holderID` is holder's account
|
||||
if (account != (*sleMptIssuance)[sfIssuer])
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
|
||||
if (!sleMpt)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
|
||||
std::uint32_t flagsOut = flagsIn;
|
||||
|
||||
// Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
|
||||
// their MPToken
|
||||
if (flags & tfMPTUnauthorize)
|
||||
{
|
||||
flagsOut &= ~lsfMPTAuthorized;
|
||||
}
|
||||
// Issuer wants to authorize a holder, set lsfMPTAuthorized on their
|
||||
// MPToken
|
||||
else
|
||||
{
|
||||
flagsOut |= lsfMPTAuthorized;
|
||||
}
|
||||
|
||||
if (flagsIn != flagsOut)
|
||||
sleMpt->setFieldU32(sfFlags, flagsOut);
|
||||
|
||||
view.update(sleMpt);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// If the account is the issuer, then no token should exist. MPTs do not
|
||||
// have the legacy ability to create such a situation, but check anyway. If
|
||||
// a token does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
|
||||
if (!mptoken)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
// Unlike a trust line, if the account is the issuer, and the token has a
|
||||
// balance, it can not just be deleted, because that will throw the issuance
|
||||
// accounting out of balance, so fail. Since this should be impossible
|
||||
// anyway, I'm not going to put any effort into it.
|
||||
if (mptoken->at(sfMPTAmount) != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return authorizeMPToken(
|
||||
view,
|
||||
{}, // priorBalance
|
||||
mptID,
|
||||
accountID,
|
||||
journal,
|
||||
tfMPTUnauthorize // flags
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType,
|
||||
int depth)
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
|
||||
|
||||
// issuer is always "authorized"
|
||||
if (mptIssuer == account) // Issuer won't have MPToken
|
||||
return tesSUCCESS;
|
||||
|
||||
bool const featureSAVEnabled = view.rules().enabled(featureSingleAssetVault);
|
||||
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
if (depth >= maxAssetCheckDepth)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// requireAuth is recursive if the issuer is a vault pseudo-account
|
||||
auto const sleIssuer = view.read(keylet::account(mptIssuer));
|
||||
if (!sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (sleIssuer->isFieldPresent(sfVaultID))
|
||||
{
|
||||
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
|
||||
if (!sleVault)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const asset = sleVault->at(sfAsset);
|
||||
if (auto const err = std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return requireAuth(view, issue, account, authType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return requireAuth(view, issue, account, authType, depth + 1);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, account);
|
||||
auto const sleToken = view.read(mptokenID);
|
||||
|
||||
// if account has no MPToken, fail
|
||||
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
|
||||
return tecNO_AUTH;
|
||||
|
||||
// Note, this check is not amendment-gated because DomainID will be always
|
||||
// empty **unless** writing to it has been enabled by an amendment
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
if (maybeDomainID)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
|
||||
"xrpl::requireAuth : issuance requires authorization");
|
||||
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
|
||||
auto const ter = credentials::validDomain(view, *maybeDomainID, account);
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
return ter; // Note: sleToken might be null
|
||||
}
|
||||
if (!sleToken)
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
// We ignore error from validDomain if we found sleToken, as it could
|
||||
// belong to someone who is explicitly authorized e.g. a vault owner.
|
||||
}
|
||||
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
// Implicitly authorize Vault and LoanBroker pseudo-accounts
|
||||
if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// mptoken must be authorized if issuance enabled requireAuth
|
||||
if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
|
||||
(!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
|
||||
return tecNO_AUTH;
|
||||
|
||||
return tesSUCCESS; // Note: sleToken might be null
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance, // for MPToken authorization
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
sleIssuance->isFlag(lsfMPTRequireAuth),
|
||||
"xrpl::enforceMPTokenAuthorization : authorization required");
|
||||
|
||||
if (account == sleIssuance->at(sfIssuer))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const keylet = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const sleToken = view.read(keylet); // NOTE: might be null
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
bool expired = false;
|
||||
bool const authorizedByDomain = [&]() -> bool {
|
||||
// NOTE: defensive here, should be checked in preclaim
|
||||
if (!maybeDomainID.has_value())
|
||||
return false; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
|
||||
if (isTesSuccess(ter))
|
||||
return true;
|
||||
if (ter == tecEXPIRED)
|
||||
expired = true;
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!authorizedByDomain && sleToken == nullptr)
|
||||
{
|
||||
// Could not find MPToken and won't create one, could be either of:
|
||||
//
|
||||
// 1. Field sfDomainID not set in MPTokenIssuance or
|
||||
// 2. Account has no matching and accepted credentials or
|
||||
// 3. Account has all expired credentials (deleted in verifyValidDomain)
|
||||
//
|
||||
// Either way, return tecNO_AUTH and there is nothing else to do
|
||||
return expired ? tecEXPIRED : tecNO_AUTH;
|
||||
}
|
||||
if (!authorizedByDomain && maybeDomainID.has_value())
|
||||
{
|
||||
// Found an MPToken but the account is not authorized and we expect
|
||||
// it to have been authorized by the domain. This could be because the
|
||||
// credentials used to create the MPToken have expired or been deleted.
|
||||
return expired ? tecEXPIRED : tecNO_AUTH;
|
||||
}
|
||||
if (!authorizedByDomain)
|
||||
{
|
||||
// We found an MPToken, but sfDomainID is not set, so this is a classic
|
||||
// MPToken which requires authorization by the token issuer.
|
||||
XRPL_ASSERT(
|
||||
sleToken != nullptr && !maybeDomainID.has_value(),
|
||||
"xrpl::enforceMPTokenAuthorization : found MPToken");
|
||||
if (sleToken->isFlag(lsfMPTAuthorized))
|
||||
return tesSUCCESS;
|
||||
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
if (authorizedByDomain && sleToken != nullptr)
|
||||
{
|
||||
// Found an MPToken, authorized by the domain. Ignore authorization flag
|
||||
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
|
||||
XRPL_ASSERT(
|
||||
maybeDomainID.has_value(),
|
||||
"xrpl::enforceMPTokenAuthorization : found MPToken for domain");
|
||||
return tesSUCCESS;
|
||||
}
|
||||
if (authorizedByDomain)
|
||||
{
|
||||
// Could not find MPToken but there should be one because we are
|
||||
// authorized by domain. Proceed to create it, then return tesSUCCESS
|
||||
XRPL_ASSERT(
|
||||
maybeDomainID.has_value() && sleToken == nullptr,
|
||||
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
|
||||
if (auto const err = authorizeMPToken(
|
||||
view,
|
||||
priorBalance, // priorBalance
|
||||
mptIssuanceID, // mptIssuanceID
|
||||
account, // account
|
||||
j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::enforceMPTokenAuthorization : condition list is incomplete");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
|
||||
{
|
||||
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
|
||||
return TER{tecNO_AUTH};
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
rippleLockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const mptIssue = amount.get<MPTIssue>();
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (amount.getIssuer() == sender)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
// 1. Decrease the MPT Holder MPTAmount
|
||||
// 2. Increase the MPT Holder EscrowedAmount
|
||||
{
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: MPToken not found for " << sender;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const amt = sle->getFieldU64(sfMPTAmount);
|
||||
auto const pay = amount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: insufficient MPTAmount for "
|
||||
<< to_string(sender) << ": " << amt << " < " << pay;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
(*sle)[sfMPTAmount] = amt - pay;
|
||||
|
||||
// Overflow check for addition
|
||||
uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
|
||||
|
||||
if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: overflow on locked amount for "
|
||||
<< to_string(sender) << ": " << locked << " + " << pay;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (sle->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*sle)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
sle->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
|
||||
view.update(sle);
|
||||
}
|
||||
|
||||
// 1. Increase the Issuance EscrowedAmount
|
||||
// 2. DO NOT change the Issuance OutstandingAmount
|
||||
{
|
||||
uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].value_or(0);
|
||||
auto const pay = amount.mpt().value();
|
||||
|
||||
// Overflow check for addition
|
||||
if (!canAdd(STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
|
||||
"locked amount for "
|
||||
<< mptIssue.getMptID() << ": " << issuanceEscrowed << " + " << pay;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (sleIssuance->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*sleIssuance)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
sleIssuance->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
|
||||
view.update(sleIssuance);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
rippleUnlockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
STAmount const& netAmount,
|
||||
STAmount const& grossAmount,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!view.rules().enabled(fixTokenEscrowV1))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
netAmount == grossAmount, "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
|
||||
}
|
||||
|
||||
auto const& issuer = netAmount.getIssuer();
|
||||
auto const& mptIssue = netAmount.get<MPTIssue>();
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
// Decrease the Issuance EscrowedAmount
|
||||
{
|
||||
if (!sleIssuance->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in issuance for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
|
||||
auto const redeem = grossAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
|
||||
<< mptIssue.getMptID() << ": " << locked << " < " << redeem;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const newLocked = locked - redeem;
|
||||
if (newLocked == 0)
|
||||
{
|
||||
sleIssuance->makeFieldAbsent(sfLockedAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
|
||||
}
|
||||
view.update(sleIssuance);
|
||||
}
|
||||
|
||||
if (issuer != receiver)
|
||||
{
|
||||
// Increase the MPT Holder MPTAmount
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto current = sle->getFieldU64(sfMPTAmount);
|
||||
auto delta = netAmount.mpt().value();
|
||||
|
||||
// Overflow check for addition
|
||||
if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
|
||||
<< to_string(receiver) << ": " << current << " + " << delta;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
(*sle)[sfMPTAmount] += delta;
|
||||
view.update(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decrease the Issuance OutstandingAmount
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const redeem = netAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
|
||||
<< mptIssue.getMptID() << ": " << outstanding << " < " << redeem;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
view.update(sleIssuance);
|
||||
}
|
||||
|
||||
if (issuer == sender)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
|
||||
"cannot unlock MPTs.";
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
// Decrease the MPT Holder EscrowedAmount
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (!sle->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
|
||||
<< to_string(sender);
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const locked = sle->getFieldU64(sfLockedAmount);
|
||||
auto const delta = grossAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
|
||||
<< to_string(sender) << ": " << locked << " < " << delta;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const newLocked = locked - delta;
|
||||
if (newLocked == 0)
|
||||
{
|
||||
sle->makeFieldAbsent(sfLockedAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sle->setFieldU64(sfLockedAmount, newLocked);
|
||||
}
|
||||
view.update(sle);
|
||||
|
||||
// Note: The gross amount is the amount that was locked, the net
|
||||
// amount is the amount that is being unlocked. The difference is the fee
|
||||
// that was charged for the transfer. If this difference is greater than
|
||||
// zero, we need to update the outstanding amount.
|
||||
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
|
||||
if (diff != 0)
|
||||
{
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
|
||||
<< mptIssue.getMptID() << ": " << outstanding << " < " << diff;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
|
||||
view.update(sleIssuance);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,58 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
//
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TER
|
||||
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
||||
{
|
||||
if (!sle)
|
||||
return tesSUCCESS;
|
||||
auto offerIndex = sle->key();
|
||||
auto owner = sle->getAccountID(sfAccount);
|
||||
|
||||
// Detect legacy directories.
|
||||
uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
|
||||
|
||||
if (!view.dirRemove(keylet::ownerDir(owner), sle->getFieldU64(sfOwnerNode), offerIndex, false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (!view.dirRemove(keylet::page(uDirectory), sle->getFieldU64(sfBookNode), offerIndex, false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (sle->isFieldPresent(sfAdditionalBooks))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
|
||||
"xrpl::offerDelete : should be a hybrid domain offer");
|
||||
|
||||
auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
|
||||
|
||||
for (auto const& bookDir : additionalBookDirs)
|
||||
{
|
||||
auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
|
||||
auto const& dirNode = bookDir.getFieldU64(sfBookNode);
|
||||
|
||||
if (!view.dirRemove(keylet::page(dirIndex), dirNode, offerIndex, false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,759 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.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/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Credit functions (from Credit.cpp)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit);
|
||||
result.setIssuer(account);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match");
|
||||
return result;
|
||||
}
|
||||
|
||||
IOUAmount
|
||||
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur)
|
||||
{
|
||||
return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
|
||||
}
|
||||
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
result = sleRippleState->getFieldAmount(sfBalance);
|
||||
if (account < issuer)
|
||||
result.negate();
|
||||
result.setIssuer(account);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match");
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
isIndividualFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
{
|
||||
if (isXRP(currency))
|
||||
return false;
|
||||
if (issuer != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can the specified account spend the specified currency issued by
|
||||
// the specified issuer or does the freeze flag prohibit it?
|
||||
bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
{
|
||||
if (isXRP(currency))
|
||||
return false;
|
||||
auto sle = view.read(keylet::account(issuer));
|
||||
if (sle && sle->isFlag(lsfGlobalFreeze))
|
||||
return true;
|
||||
if (issuer != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
{
|
||||
if (isXRP(currency))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (issuer == account)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (!sle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Trust line operations
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // --> ripple state entry
|
||||
SLE::ref sleAccount, // --> the account being set.
|
||||
bool const bAuth, // --> authorize account.
|
||||
bool const bNoRipple, // --> others cannot ripple through
|
||||
bool const bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j)
|
||||
{
|
||||
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
|
||||
<< to_string(uDstAccountID) << ", " << saBalance.getFullText();
|
||||
|
||||
auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
|
||||
auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
|
||||
if (uLowAccountID == uHighAccountID)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::trustCreate : trust line to self");
|
||||
if (view.rules().enabled(featureLendingProtocol))
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
|
||||
view.insert(sleRippleState);
|
||||
|
||||
auto lowNode = view.dirInsert(
|
||||
keylet::ownerDir(uLowAccountID), sleRippleState->key(), describeOwnerDir(uLowAccountID));
|
||||
|
||||
if (!lowNode)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
auto highNode = view.dirInsert(
|
||||
keylet::ownerDir(uHighAccountID), sleRippleState->key(), describeOwnerDir(uHighAccountID));
|
||||
|
||||
if (!highNode)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
|
||||
bool const bSetHigh = bSrcHigh ^ bSetDst;
|
||||
|
||||
XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
|
||||
if (!sleAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
sleAccount->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
|
||||
"xrpl::trustCreate : matching account ID");
|
||||
auto const slePeer = view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
|
||||
if (!slePeer)
|
||||
return tecNO_TARGET;
|
||||
|
||||
// Remember deletion hints.
|
||||
sleRippleState->setFieldU64(sfLowNode, *lowNode);
|
||||
sleRippleState->setFieldU64(sfHighNode, *highNode);
|
||||
|
||||
sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
|
||||
sleRippleState->setFieldAmount(
|
||||
bSetHigh ? sfLowLimit : sfHighLimit,
|
||||
STAmount(Issue{saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
|
||||
|
||||
if (uQualityIn)
|
||||
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
|
||||
|
||||
if (uQualityOut)
|
||||
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
|
||||
|
||||
std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
|
||||
|
||||
if (bAuth)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
|
||||
}
|
||||
if (bNoRipple)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
if (bFreeze)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
|
||||
}
|
||||
if (bDeepFreeze)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
|
||||
{
|
||||
// The other side's default is no rippling
|
||||
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
}
|
||||
|
||||
sleRippleState->setFieldU32(sfFlags, uFlags);
|
||||
adjustOwnerCount(view, sleAccount, 1, j);
|
||||
|
||||
// ONLY: Create ripple balance.
|
||||
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
|
||||
|
||||
view.creditHook(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
trustDelete(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j)
|
||||
{
|
||||
// Detect legacy dirs.
|
||||
std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
|
||||
std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
|
||||
|
||||
if (!view.dirRemove(keylet::ownerDir(uLowAccountID), uLowNode, sleRippleState->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
|
||||
|
||||
if (!view.dirRemove(keylet::ownerDir(uHighAccountID), uHighNode, sleRippleState->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
|
||||
view.erase(sleRippleState);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// IOU issuance/redemption
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static bool
|
||||
updateTrustLine(
|
||||
ApplyView& view,
|
||||
SLE::pointer state,
|
||||
bool bSenderHigh,
|
||||
AccountID const& sender,
|
||||
STAmount const& before,
|
||||
STAmount const& after,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!state)
|
||||
return false;
|
||||
std::uint32_t const flags(state->getFieldU32(sfFlags));
|
||||
|
||||
auto sle = view.peek(keylet::account(sender));
|
||||
if (!sle)
|
||||
return false;
|
||||
|
||||
// YYY Could skip this if rippling in reverse.
|
||||
if (before > beast::zero
|
||||
// Sender balance was positive.
|
||||
&& after <= beast::zero
|
||||
// Sender is zero or negative.
|
||||
&& (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
|
||||
// Sender reserve is set.
|
||||
&& static_cast<bool>(flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
|
||||
static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
|
||||
!(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
|
||||
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
// Sender trust limit is 0.
|
||||
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
|
||||
// Sender quality in is 0.
|
||||
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
|
||||
// Sender quality out is 0.
|
||||
{
|
||||
// VFALCO Where is the line being deleted?
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
|
||||
// Clear reserve flag.
|
||||
state->setFieldU32(sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
|
||||
|
||||
// Balance is zero, receiver reserve is clear.
|
||||
if (!after // Balance is zero.
|
||||
&& !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TER
|
||||
issueIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!isXRP(account) && !isXRP(issue.account),
|
||||
"xrpl::issueIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account");
|
||||
|
||||
JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": " << amount.getFullText();
|
||||
|
||||
bool bSenderHigh = issue.account > account;
|
||||
|
||||
auto const index = keylet::line(issue.account, account, issue.currency);
|
||||
|
||||
if (auto state = view.peek(index))
|
||||
{
|
||||
STAmount final_balance = state->getFieldAmount(sfBalance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate(); // Put balance in sender terms.
|
||||
|
||||
STAmount const start_balance = final_balance;
|
||||
|
||||
final_balance -= amount;
|
||||
|
||||
auto const must_delete = updateTrustLine(
|
||||
view, state, bSenderHigh, issue.account, start_balance, final_balance, j);
|
||||
|
||||
view.creditHook(issue.account, account, amount, start_balance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate();
|
||||
|
||||
// Adjust the balance on the trust line if necessary. We do this even
|
||||
// if we are going to delete the line to reflect the correct balance
|
||||
// at the time of deletion.
|
||||
state->setFieldAmount(sfBalance, final_balance);
|
||||
if (must_delete)
|
||||
{
|
||||
return trustDelete(
|
||||
view,
|
||||
state,
|
||||
bSenderHigh ? account : issue.account,
|
||||
bSenderHigh ? issue.account : account,
|
||||
j);
|
||||
}
|
||||
|
||||
view.update(state);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// NIKB TODO: The limit uses the receiver's account as the issuer and
|
||||
// this is unnecessarily inefficient as copying which could be avoided
|
||||
// is now required. Consider available options.
|
||||
STAmount const limit(Issue{issue.currency, account});
|
||||
STAmount final_balance = amount;
|
||||
|
||||
final_balance.setIssuer(noAccount());
|
||||
|
||||
auto const receiverAccount = view.peek(keylet::account(account));
|
||||
if (!receiverAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
bSenderHigh,
|
||||
issue.account,
|
||||
account,
|
||||
index.key,
|
||||
receiverAccount,
|
||||
false,
|
||||
noRipple,
|
||||
false,
|
||||
false,
|
||||
final_balance,
|
||||
limit,
|
||||
0,
|
||||
0,
|
||||
j);
|
||||
}
|
||||
|
||||
TER
|
||||
redeemIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!isXRP(account) && !isXRP(issue.account),
|
||||
"xrpl::redeemIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account");
|
||||
|
||||
JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": " << amount.getFullText();
|
||||
|
||||
bool bSenderHigh = account > issue.account;
|
||||
|
||||
if (auto state = view.peek(keylet::line(account, issue.account, issue.currency)))
|
||||
{
|
||||
STAmount final_balance = state->getFieldAmount(sfBalance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate(); // Put balance in sender terms.
|
||||
|
||||
STAmount const start_balance = final_balance;
|
||||
|
||||
final_balance -= amount;
|
||||
|
||||
auto const must_delete =
|
||||
updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j);
|
||||
|
||||
view.creditHook(account, issue.account, amount, start_balance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate();
|
||||
|
||||
// Adjust the balance on the trust line if necessary. We do this even
|
||||
// if we are going to delete the line to reflect the correct balance
|
||||
// at the time of deletion.
|
||||
state->setFieldAmount(sfBalance, final_balance);
|
||||
|
||||
if (must_delete)
|
||||
{
|
||||
return trustDelete(
|
||||
view,
|
||||
state,
|
||||
bSenderHigh ? issue.account : account,
|
||||
bSenderHigh ? account : issue.account,
|
||||
j);
|
||||
}
|
||||
|
||||
view.update(state);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// In order to hold an IOU, a trust line *MUST* exist to track the
|
||||
// balance. If it doesn't, then something is very wrong. Don't try
|
||||
// to continue.
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "redeemIOU: " << to_string(account) << " attempts to "
|
||||
<< "redeem " << amount.getFullText() << " but no trust line exists!";
|
||||
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account, AuthType authType)
|
||||
{
|
||||
if (isXRP(issue) || issue.account == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const trustLine = view.read(keylet::line(account, issue.account, issue.currency));
|
||||
// If account has no line, and this is a strong check, fail
|
||||
if (!trustLine && authType == AuthType::StrongAuth)
|
||||
return tecNO_LINE;
|
||||
|
||||
// If this is a weak or legacy check, or if the account has a line, fail if
|
||||
// auth is required and not set on the line
|
||||
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
|
||||
{
|
||||
if (trustLine)
|
||||
{
|
||||
return ((*trustLine)[sfFlags] & ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
|
||||
? tesSUCCESS
|
||||
: TER{tecNO_AUTH};
|
||||
}
|
||||
return TER{tecNO_LINE};
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
|
||||
{
|
||||
if (issue.native())
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
if (issuerId == from || issuerId == to)
|
||||
return tesSUCCESS;
|
||||
auto const sleIssuer = view.read(keylet::account(issuerId));
|
||||
if (sleIssuer == nullptr)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const isRippleDisabled = [&](AccountID account) -> bool {
|
||||
// Line might not exist, but some transfers can create it. If this
|
||||
// is the case, just check the default ripple on the issuer account.
|
||||
auto const line = view.read(keylet::line(account, issue));
|
||||
if (line)
|
||||
{
|
||||
bool const issuerHigh = issuerId > account;
|
||||
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
return sleIssuer->isFlag(lsfDefaultRipple) == false;
|
||||
};
|
||||
|
||||
// Fail if rippling disabled on both trust lines
|
||||
if (isRippleDisabled(from) && isRippleDisabled(to))
|
||||
return terNO_RIPPLE;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Every account can hold XRP. An issuer can issue directly.
|
||||
if (issue.native() || accountID == issue.getIssuer())
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
auto const& currency = issue.currency;
|
||||
if (isGlobalFrozen(view, issuerId))
|
||||
return tecFROZEN; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& srcId = issuerId;
|
||||
auto const& dstId = accountID;
|
||||
auto const high = srcId > dstId;
|
||||
auto const index = keylet::line(srcId, dstId, currency);
|
||||
auto const sleSrc = view.peek(keylet::account(srcId));
|
||||
auto const sleDst = view.peek(keylet::account(dstId));
|
||||
if (!sleDst || !sleSrc)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (!sleSrc->isFlag(lsfDefaultRipple))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
// If the line already exists, don't create it again.
|
||||
if (view.read(index))
|
||||
return tecDUPLICATE;
|
||||
|
||||
// Can the account cover the trust line reserve ?
|
||||
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
|
||||
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
high,
|
||||
srcId,
|
||||
dstId,
|
||||
index.key,
|
||||
sleDst,
|
||||
/*bAuth=*/false,
|
||||
/*bNoRipple=*/true,
|
||||
/*bFreeze=*/false,
|
||||
/*deepFreeze*/ false,
|
||||
/*saBalance=*/STAmount{Issue{currency, noAccount()}},
|
||||
/*saLimit=*/STAmount{Issue{currency, dstId}},
|
||||
/*uQualityIn=*/0,
|
||||
/*uQualityOut=*/0,
|
||||
journal);
|
||||
}
|
||||
|
||||
TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
if (issue.native())
|
||||
{
|
||||
auto const sle = view.read(keylet::account(accountID));
|
||||
if (!sle)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = sle->getFieldAmount(sfBalance);
|
||||
if (balance.xrp() != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// `asset` is an IOU.
|
||||
// If the account is the issuer, then no line should exist. Check anyway.
|
||||
// If a line does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == issue.account;
|
||||
auto const line = view.peek(keylet::line(accountID, issue));
|
||||
if (!line)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// Adjust the owner count(s)
|
||||
if (line->isFlag(lsfLowReserve))
|
||||
{
|
||||
// Clear reserve for low account.
|
||||
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
|
||||
if (!sleLowAccount)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleLowAccount, -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);
|
||||
}
|
||||
|
||||
if (line->isFlag(lsfHighReserve))
|
||||
{
|
||||
// Clear reserve for high account.
|
||||
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
|
||||
if (!sleHighAccount)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleHighAccount, -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);
|
||||
}
|
||||
|
||||
return trustDelete(
|
||||
view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal);
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMTrustLine(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleState,
|
||||
std::optional<AccountID> const& ammAccountID,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!sleState || sleState->getType() != ltRIPPLE_STATE)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& [low, high] = std::minmax(
|
||||
sleState->getFieldAmount(sfLowLimit).getIssuer(),
|
||||
sleState->getFieldAmount(sfHighLimit).getIssuer());
|
||||
auto sleLow = view.peek(keylet::account(low));
|
||||
auto sleHigh = view.peek(keylet::account(high));
|
||||
if (!sleLow || !sleHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const ammLow = sleLow->isFieldPresent(sfAMMID);
|
||||
bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
|
||||
|
||||
// can't both be AMM
|
||||
if (ammLow && ammHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// at least one must be
|
||||
if (!ammLow && !ammHigh)
|
||||
return terNO_AMM;
|
||||
|
||||
// one must be the target amm
|
||||
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
|
||||
return terNO_AMM;
|
||||
|
||||
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
|
||||
if (!(sleState->getFlags() & uFlags))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,112 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
//
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets)
|
||||
{
|
||||
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
|
||||
XRPL_ASSERT(
|
||||
assets.asset() == vault->at(sfAsset),
|
||||
"xrpl::assetsToSharesDeposit : assets and vault match");
|
||||
if (assets.negative() || assets.asset() != vault->at(sfAsset))
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
Number const assetTotal = vault->at(sfAssetsTotal);
|
||||
STAmount shares{vault->at(sfShareMPTID)};
|
||||
if (assetTotal == 0)
|
||||
{
|
||||
return STAmount{
|
||||
shares.asset(),
|
||||
Number(assets.mantissa(), assets.exponent() + vault->at(sfScale)).truncate()};
|
||||
}
|
||||
|
||||
Number const shareTotal = issuance->at(sfOutstandingAmount);
|
||||
shares = ((shareTotal * assets) / assetTotal).truncate();
|
||||
return shares;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares)
|
||||
{
|
||||
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
|
||||
XRPL_ASSERT(
|
||||
shares.asset() == vault->at(sfShareMPTID),
|
||||
"xrpl::sharesToAssetsDeposit : shares and vault match");
|
||||
if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
Number const assetTotal = vault->at(sfAssetsTotal);
|
||||
STAmount assets{vault->at(sfAsset)};
|
||||
if (assetTotal == 0)
|
||||
{
|
||||
return STAmount{
|
||||
assets.asset(), shares.mantissa(), shares.exponent() - vault->at(sfScale), false};
|
||||
}
|
||||
|
||||
Number const shareTotal = issuance->at(sfOutstandingAmount);
|
||||
assets = (assetTotal * shares) / shareTotal;
|
||||
return assets;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets,
|
||||
TruncateShares truncate)
|
||||
{
|
||||
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesWithdraw : non-negative assets");
|
||||
XRPL_ASSERT(
|
||||
assets.asset() == vault->at(sfAsset),
|
||||
"xrpl::assetsToSharesWithdraw : assets and vault match");
|
||||
if (assets.negative() || assets.asset() != vault->at(sfAsset))
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
Number assetTotal = vault->at(sfAssetsTotal);
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
STAmount shares{vault->at(sfShareMPTID)};
|
||||
if (assetTotal == 0)
|
||||
return shares;
|
||||
Number const shareTotal = issuance->at(sfOutstandingAmount);
|
||||
Number result = (shareTotal * assets) / assetTotal;
|
||||
if (truncate == TruncateShares::yes)
|
||||
result = result.truncate();
|
||||
shares = result;
|
||||
return shares;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares)
|
||||
{
|
||||
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
|
||||
XRPL_ASSERT(
|
||||
shares.asset() == vault->at(sfShareMPTID),
|
||||
"xrpl::sharesToAssetsWithdraw : shares and vault match");
|
||||
if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
Number assetTotal = vault->at(sfAssetsTotal);
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
STAmount assets{vault->at(sfAsset)};
|
||||
if (assetTotal == 0)
|
||||
return assets;
|
||||
Number const shareTotal = issuance->at(sfOutstandingAmount);
|
||||
assets = (assetTotal * shares) / shareTotal;
|
||||
return assets;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
68
src/libxrpl/nodestore/backend/Backend.cpp
Normal file
68
src/libxrpl/nodestore/backend/Backend.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include <xrpl/nodestore/Backend.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
namespace xrpl {
|
||||
namespace NodeStore {
|
||||
|
||||
// Initialize the static constant for hardware thread count. The `hardware_concurrency` function can
|
||||
// return 0 on some platforms, in which case we default to 1. We limit the total number of threads
|
||||
// to 8 to avoid contention.
|
||||
unsigned int const Backend::numHardwareThreads = []() {
|
||||
auto const hw = std::thread::hardware_concurrency();
|
||||
return std::min(std::max(hw, 1u), 8u);
|
||||
}();
|
||||
|
||||
std::pair<unsigned int, unsigned int>
|
||||
Backend::calculateBatchParallelism(unsigned int batchSize, unsigned int maxThreadCount)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
maxThreadCount > 0,
|
||||
"xrpl::NodeStore::Backend::calculateBatchParallelism : maxThreadCount > 0");
|
||||
if (maxThreadCount == 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::NodeStore::Backend::calculateBatchParallelism : maxThreadCount == 0");
|
||||
return {1, batchSize};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (batchSize == 0)
|
||||
{
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
// Estimate the number of threads using ceiling division: aim for at least 4 items per thread,
|
||||
// but don't exceed the number of available threads.
|
||||
auto const initialThreads = std::min((batchSize + 3u) / 4u, maxThreadCount);
|
||||
|
||||
// Calculate number of items per thread.
|
||||
auto const numItems = (batchSize + initialThreads - 1u) / initialThreads;
|
||||
|
||||
// Calculate the actual number of threads needed. After rounding up numItems, we may need fewer
|
||||
// threads than initially estimated.
|
||||
auto const actualThreads = (batchSize + numItems - 1u) / numItems;
|
||||
|
||||
XRPL_ASSERT(
|
||||
numItems <= batchSize,
|
||||
"xrpl::NodeStore::Backend::calculateBatchParallelism : numItems <= batchSize");
|
||||
XRPL_ASSERT(
|
||||
actualThreads <= batchSize,
|
||||
"xrpl::NodeStore::Backend::calculateBatchParallelism : actualThreads <= batchSize");
|
||||
XRPL_ASSERT(
|
||||
actualThreads <= maxThreadCount,
|
||||
"xrpl::NodeStore::Backend::calculateBatchParallelism : actualThreads <= hwThreadCount");
|
||||
if (numItems > batchSize || actualThreads > batchSize || actualThreads > maxThreadCount)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::NodeStore::Backend::calculateBatchParallelism : sanity check failed");
|
||||
return {1, batchSize};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {actualThreads, numItems};
|
||||
}
|
||||
|
||||
} // namespace NodeStore
|
||||
} // namespace xrpl
|
||||
@@ -7,15 +7,22 @@
|
||||
#include <xrpl/nodestore/detail/EncodedBlob.h>
|
||||
#include <xrpl/nodestore/detail/codec.h>
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/thread_pool.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <nudb/nudb.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <latch>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
namespace NodeStore {
|
||||
@@ -23,21 +30,6 @@ namespace NodeStore {
|
||||
class NuDBBackend : public Backend
|
||||
{
|
||||
public:
|
||||
// "appnum" is an application-defined constant stored in the header of a
|
||||
// NuDB database. We used it to identify shard databases before that code
|
||||
// was removed. For now, its only use is a sanity check that the database
|
||||
// was created by xrpld.
|
||||
static constexpr std::uint64_t appnum = 1;
|
||||
|
||||
beast::Journal const j_;
|
||||
size_t const keyBytes_;
|
||||
std::size_t const burstSize_;
|
||||
std::string const name_;
|
||||
std::size_t const blockSize_;
|
||||
nudb::store db_;
|
||||
std::atomic<bool> deletePath_;
|
||||
Scheduler& scheduler_;
|
||||
|
||||
NuDBBackend(
|
||||
size_t keyBytes,
|
||||
Section const& keyValues,
|
||||
@@ -51,6 +43,7 @@ public:
|
||||
, blockSize_(parseBlockSize(name_, keyValues, journal))
|
||||
, deletePath_(false)
|
||||
, scheduler_(scheduler)
|
||||
, threadPool_(numHardwareThreads)
|
||||
{
|
||||
if (name_.empty())
|
||||
Throw<std::runtime_error>("nodestore: Missing path in NuDB backend");
|
||||
@@ -71,6 +64,7 @@ public:
|
||||
, db_(context)
|
||||
, deletePath_(false)
|
||||
, scheduler_(scheduler)
|
||||
, threadPool_(numHardwareThreads)
|
||||
{
|
||||
if (name_.empty())
|
||||
Throw<std::runtime_error>("nodestore: Missing path in NuDB backend");
|
||||
@@ -80,7 +74,32 @@ public:
|
||||
{
|
||||
try
|
||||
{
|
||||
// close can throw and we don't want the destructor to throw.
|
||||
// Set shutdown flag to prevent new batch operations from starting. This must happen
|
||||
// before stop() is called to ensure fetchBatch/storeBatch check the flag before posting
|
||||
// any new tasks.
|
||||
shutdown_.store(true, std::memory_order_release);
|
||||
|
||||
// Wait for all active operations to complete.
|
||||
while (pendingReads_.load(std::memory_order_acquire) > 0 ||
|
||||
pendingWrites_.load(std::memory_order_acquire) > 0)
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
// Signal the thread pool to stop accepting new work. This ensures no new tasks will be
|
||||
// posted after this point.
|
||||
threadPool_.stop();
|
||||
|
||||
// Wait for all currently executing thread pool tasks to complete. This prevents worker
|
||||
// threads from accessing the database after close().
|
||||
threadPool_.join();
|
||||
|
||||
// Verify all writes have completed.
|
||||
XRPL_ASSERT(
|
||||
pendingWrites_.load() == 0, "xrpl::NuDBBackend::~NuDBBackend : pendingWrites == 0");
|
||||
|
||||
// Close the database. At this point, all threads have stopped and no pending reads and
|
||||
// writes remain, so it's safe to close the database.
|
||||
close();
|
||||
}
|
||||
catch (nudb::system_error const&) // NOLINT(bugprone-empty-catch)
|
||||
@@ -109,9 +128,7 @@ public:
|
||||
if (db_.is_open())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::NodeStore::NuDBBackend::open : database is already "
|
||||
"open");
|
||||
UNREACHABLE("xrpl::NodeStore::NuDBBackend::open : database is already open");
|
||||
JLOG(j_.error()) << "database is already open";
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
@@ -127,16 +144,24 @@ public:
|
||||
nudb::create<nudb::xxhasher>(
|
||||
dp, kp, lp, appType, uid, salt, keyBytes_, blockSize_, 0.50, ec);
|
||||
if (ec == nudb::errc::file_exists)
|
||||
{
|
||||
ec = {};
|
||||
}
|
||||
if (ec)
|
||||
{
|
||||
Throw<nudb::system_error>(ec);
|
||||
}
|
||||
}
|
||||
db_.open(dp, kp, lp, ec);
|
||||
if (ec)
|
||||
{
|
||||
Throw<nudb::system_error>(ec);
|
||||
}
|
||||
|
||||
if (db_.appnum() != appnum)
|
||||
{
|
||||
Throw<std::runtime_error>("nodestore: unknown appnum");
|
||||
}
|
||||
db_.set_burst(burstSize_);
|
||||
}
|
||||
|
||||
@@ -181,9 +206,22 @@ public:
|
||||
Status
|
||||
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pno) override
|
||||
{
|
||||
// Increment pending reads counter on entry, decrement on exit. This ensures the destructor
|
||||
// waits for this operation to complete.
|
||||
++pendingReads_;
|
||||
auto guard = [this](void*) { --pendingReads_; };
|
||||
std::unique_ptr<void, decltype(guard)> opGuard(reinterpret_cast<void*>(1), guard);
|
||||
|
||||
// Check if we're shutting down. If so, return immediately instead of doing any work.
|
||||
if (shutdown_.load(std::memory_order_acquire))
|
||||
{
|
||||
return backendError;
|
||||
}
|
||||
|
||||
Status status = ok;
|
||||
pno->reset();
|
||||
nudb::error_code ec;
|
||||
|
||||
db_.fetch(
|
||||
hash.data(),
|
||||
[&hash, pno, &status](void const* data, std::size_t size) {
|
||||
@@ -199,30 +237,119 @@ public:
|
||||
status = ok;
|
||||
},
|
||||
ec);
|
||||
|
||||
if (ec == nudb::error::key_not_found)
|
||||
{
|
||||
return notFound;
|
||||
}
|
||||
if (ec)
|
||||
{
|
||||
Throw<nudb::system_error>(ec);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
std::pair<std::vector<std::shared_ptr<NodeObject>>, Status>
|
||||
fetchBatch(std::vector<uint256> const& hashes) override
|
||||
{
|
||||
std::vector<std::shared_ptr<NodeObject>> results;
|
||||
results.reserve(hashes.size());
|
||||
for (auto const& h : hashes)
|
||||
if (hashes.empty())
|
||||
{
|
||||
std::shared_ptr<NodeObject> nObj;
|
||||
Status status = fetch(h, &nObj);
|
||||
if (status != ok)
|
||||
return {{}, ok};
|
||||
}
|
||||
|
||||
// Increment pending reads counter on entry, decrement on exit. This ensures the destructor
|
||||
// waits for this operation to complete.
|
||||
pendingReads_ += hashes.size();
|
||||
auto guard = [this, &hashes](void*) { pendingReads_ -= hashes.size(); };
|
||||
std::unique_ptr<void, decltype(guard)> opGuard(reinterpret_cast<void*>(1), guard);
|
||||
|
||||
// Check if we're shutting down. If so, return immediately instead of doing any work.
|
||||
if (shutdown_.load(std::memory_order_acquire))
|
||||
{
|
||||
return {{}, backendError};
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<NodeObject>> results(hashes.size());
|
||||
|
||||
// Calculate parallelization parameters for the batch.
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(hashes.size(), numHardwareThreads);
|
||||
|
||||
// If we need only one thread, just do it sequentially. Although it should be impossible to
|
||||
// get 0 threads here, handle it gracefully just in case.
|
||||
if (numThreads <= 1u)
|
||||
{
|
||||
for (size_t i = 0; i < hashes.size(); ++i)
|
||||
{
|
||||
results.push_back({});
|
||||
std::shared_ptr<NodeObject> nObj;
|
||||
if (fetch(hashes[i], &nObj) == ok)
|
||||
{
|
||||
results[i] = nObj;
|
||||
}
|
||||
}
|
||||
else
|
||||
return {results, ok};
|
||||
}
|
||||
|
||||
// Use a latch to synchronize task completion.
|
||||
std::latch taskCompletion(numThreads);
|
||||
|
||||
// Track exceptions from worker threads.
|
||||
std::exception_ptr eptr;
|
||||
std::mutex emutex;
|
||||
|
||||
// Submit fetch tasks to the thread pool.
|
||||
for (auto t = 0u; t < numThreads; ++t)
|
||||
{
|
||||
auto const startIdx = t * numItems;
|
||||
XRPL_ASSERT(
|
||||
startIdx < hashes.size(),
|
||||
"xrpl::NuDBFactory::fetchBatch : startIdx < hashes.size()");
|
||||
if (startIdx >= hashes.size())
|
||||
{
|
||||
results.push_back(nObj);
|
||||
// This should never happen, but is kept as a safety check.
|
||||
taskCompletion.count_down();
|
||||
continue;
|
||||
}
|
||||
auto const endIdx = std::min<std::size_t>(startIdx + numItems, hashes.size());
|
||||
|
||||
auto task =
|
||||
[this, &hashes, &results, &taskCompletion, &eptr, &emutex, startIdx, endIdx]() {
|
||||
try
|
||||
{
|
||||
// Fetch the items assigned to this task.
|
||||
for (size_t i = startIdx; i < endIdx; ++i)
|
||||
{
|
||||
std::shared_ptr<NodeObject> nObj;
|
||||
if (fetch(hashes[i], &nObj) == ok)
|
||||
{
|
||||
results[i] = nObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Store the first exception that occurs. Ensures count_down() is always
|
||||
// called to prevent deadlock.
|
||||
std::lock_guard<std::mutex> lock(emutex);
|
||||
if (!eptr)
|
||||
{
|
||||
eptr = std::current_exception();
|
||||
}
|
||||
}
|
||||
// Signal task completion.
|
||||
taskCompletion.count_down();
|
||||
};
|
||||
|
||||
boost::asio::post(threadPool_, std::move(task));
|
||||
}
|
||||
|
||||
// Wait for all fetch tasks to complete.
|
||||
taskCompletion.wait();
|
||||
|
||||
// Rethrow the first exception if one occurred.
|
||||
if (eptr)
|
||||
{
|
||||
std::rethrow_exception(eptr);
|
||||
}
|
||||
|
||||
return {results, ok};
|
||||
@@ -232,21 +359,39 @@ public:
|
||||
do_insert(std::shared_ptr<NodeObject> const& no)
|
||||
{
|
||||
EncodedBlob e(no);
|
||||
nudb::error_code ec;
|
||||
|
||||
nudb::detail::buffer bf;
|
||||
auto const result = nodeobject_compress(e.getData(), e.getSize(), bf);
|
||||
|
||||
nudb::error_code ec;
|
||||
db_.insert(e.getKey(), result.first, result.second, ec);
|
||||
if (ec && ec != nudb::error::key_exists)
|
||||
{
|
||||
Throw<nudb::system_error>(ec);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
store(std::shared_ptr<NodeObject> const& no) override
|
||||
{
|
||||
// Increment pending writes counter on entry, decrement on exit. This ensures the destructor
|
||||
// waits for this operation to complete.
|
||||
++pendingWrites_;
|
||||
auto guard = [this](void*) { --pendingWrites_; };
|
||||
std::unique_ptr<void, decltype(guard)> opGuard(reinterpret_cast<void*>(1), guard);
|
||||
|
||||
// Check if we're shutting down. If so, return immediately instead of doing any work.
|
||||
if (shutdown_.load(std::memory_order_acquire))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BatchWriteReport report{};
|
||||
report.writeCount = 1;
|
||||
auto const start = std::chrono::steady_clock::now();
|
||||
|
||||
do_insert(no);
|
||||
|
||||
report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
scheduler_.onBatchWrite(report);
|
||||
@@ -255,11 +400,127 @@ public:
|
||||
void
|
||||
storeBatch(Batch const& batch) override
|
||||
{
|
||||
if (batch.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment pending writes counter on entry, decrement on exit. This ensures the destructor
|
||||
// waits for this operation to complete.
|
||||
pendingWrites_ += batch.size();
|
||||
auto guard = [this, &batch](void*) { pendingWrites_ -= batch.size(); };
|
||||
std::unique_ptr<void, decltype(guard)> opGuard(reinterpret_cast<void*>(1), guard);
|
||||
|
||||
// Check if we're shutting down. If so, return immediately instead of doing any work.
|
||||
if (shutdown_.load(std::memory_order_acquire))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BatchWriteReport report{};
|
||||
report.writeCount = batch.size();
|
||||
auto const start = std::chrono::steady_clock::now();
|
||||
for (auto const& e : batch)
|
||||
do_insert(e);
|
||||
|
||||
// Calculate parallelization parameters for the batch.
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batch.size(), numHardwareThreads);
|
||||
|
||||
// If we need only one thread, just do it sequentially. Although it should be impossible to
|
||||
// get 0 threads here, handle it gracefully just in case.
|
||||
if (numThreads <= 1u)
|
||||
{
|
||||
for (auto const& e : batch)
|
||||
{
|
||||
do_insert(e);
|
||||
}
|
||||
|
||||
report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
scheduler_.onBatchWrite(report);
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper struct that stores actual item data, not pointers, to avoid dangling references
|
||||
// after EncodedBlob and buffer go out of scope in the thread.
|
||||
struct CompressedData
|
||||
{
|
||||
std::vector<std::uint8_t> key;
|
||||
std::vector<std::uint8_t> data;
|
||||
std::exception_ptr eptr;
|
||||
};
|
||||
std::vector<CompressedData> compressed(batch.size());
|
||||
|
||||
// Use a latch to synchronize task completion.
|
||||
std::latch taskCompletion(numThreads);
|
||||
|
||||
// Submit compression tasks to the thread pool.
|
||||
for (auto t = 0u; t < numThreads; ++t)
|
||||
{
|
||||
auto const startIdx = t * numItems;
|
||||
XRPL_ASSERT(
|
||||
startIdx < batch.size(), "xrpl::NuDBFactory::storeBatch : startIdx < batch.size()");
|
||||
if (startIdx >= batch.size())
|
||||
{
|
||||
// This should never happen, but is kept as a safety check.
|
||||
taskCompletion.count_down();
|
||||
continue;
|
||||
}
|
||||
auto const endIdx = std::min<std::size_t>(startIdx + numItems, batch.size());
|
||||
|
||||
auto task =
|
||||
[&batch, &compressed, &taskCompletion, startIdx, endIdx, keyBytes = keyBytes_]() {
|
||||
// Compress the items assigned to this task.
|
||||
for (size_t i = startIdx; i < endIdx; ++i)
|
||||
{
|
||||
auto& item = compressed[i];
|
||||
try
|
||||
{
|
||||
EncodedBlob e(batch[i]);
|
||||
|
||||
// Copy the key data to avoid dangling pointer.
|
||||
auto const* keyPtr = static_cast<std::uint8_t const*>(e.getKey());
|
||||
item.key.assign(keyPtr, keyPtr + keyBytes);
|
||||
|
||||
// Compress and copy the data to avoid dangling pointer.
|
||||
nudb::detail::buffer bf;
|
||||
auto const comp = nodeobject_compress(e.getData(), e.getSize(), bf);
|
||||
auto const* dataPtr = static_cast<std::uint8_t const*>(comp.first);
|
||||
item.data.assign(dataPtr, dataPtr + comp.second);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Store the exception so it can be rethrown in the sequential phase
|
||||
// below.
|
||||
item.eptr = std::current_exception();
|
||||
}
|
||||
}
|
||||
// Signal task completion.
|
||||
taskCompletion.count_down();
|
||||
};
|
||||
|
||||
boost::asio::post(threadPool_, std::move(task));
|
||||
}
|
||||
|
||||
// Wait for all compression tasks to complete.
|
||||
taskCompletion.wait();
|
||||
|
||||
// Insert the compressed data sequentially, since NuDB is designed as an append-only data
|
||||
// store that limits concurrent writes.
|
||||
for (auto const& item : compressed)
|
||||
{
|
||||
if (item.eptr)
|
||||
{
|
||||
std::rethrow_exception(item.eptr);
|
||||
}
|
||||
|
||||
nudb::error_code ec;
|
||||
db_.insert(item.key.data(), item.data.data(), item.data.size(), ec);
|
||||
if (ec && ec != nudb::error::key_exists)
|
||||
{
|
||||
Throw<nudb::system_error>(ec);
|
||||
}
|
||||
}
|
||||
|
||||
report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
scheduler_.onBatchWrite(report);
|
||||
@@ -276,7 +537,7 @@ public:
|
||||
auto const dp = db_.dat_path();
|
||||
auto const kp = db_.key_path();
|
||||
auto const lp = db_.log_path();
|
||||
// auto const appnum = db_.appnum();
|
||||
|
||||
nudb::error_code ec;
|
||||
db_.close(ec);
|
||||
if (ec)
|
||||
@@ -310,7 +571,7 @@ public:
|
||||
int
|
||||
getWriteLoad() override
|
||||
{
|
||||
return 0;
|
||||
return pendingWrites_.load();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -385,6 +646,28 @@ private:
|
||||
Throw<std::runtime_error>(s.str());
|
||||
}
|
||||
}
|
||||
|
||||
// "appnum" is an application-defined constant stored in the header of a
|
||||
// NuDB database. We used it to identify shard databases before that code
|
||||
// was removed. For now, its only use is a sanity check that the database
|
||||
// was created by xrpld.
|
||||
static constexpr std::uint64_t appnum = 1;
|
||||
|
||||
beast::Journal const j_;
|
||||
size_t const keyBytes_;
|
||||
std::size_t const burstSize_;
|
||||
std::string const name_;
|
||||
std::size_t const blockSize_;
|
||||
nudb::store db_;
|
||||
std::atomic<bool> deletePath_;
|
||||
Scheduler& scheduler_;
|
||||
std::atomic<size_t> pendingReads_{
|
||||
0}; // Declare before threadPool_ to ensure it's destroyed after.
|
||||
std::atomic<size_t> pendingWrites_{
|
||||
0}; // Declare before threadPool_ to ensure it's destroyed after.
|
||||
std::atomic<bool> shutdown_{
|
||||
false}; // Declare before threadPool_ to ensure it's destroyed after.
|
||||
boost::asio::thread_pool threadPool_; // Declare after db_ to ensure it's destroyed before.
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace xrpl {
|
||||
namespace NodeStore {
|
||||
@@ -185,6 +186,41 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Enable pipelined writes for better write concurrency.
|
||||
m_options.enable_pipelined_write = true;
|
||||
|
||||
// Set background job parallelism for better compaction/flush performance to the number of
|
||||
// hardware threads, unless the value is explicitly provided in the config. The default is
|
||||
// 2 (see include/rocksdb/options.h in the Conan dependency directory), so don't use fewer
|
||||
// than that if no value is explicitly provided.
|
||||
if (keyValues.exists("max_background_jobs"))
|
||||
{
|
||||
m_options.max_background_jobs = get<unsigned int>(keyValues, "max_background_jobs");
|
||||
}
|
||||
else if (auto v = numHardwareThreads; v > 2)
|
||||
{
|
||||
m_options.max_background_jobs = v;
|
||||
}
|
||||
|
||||
// Set subcompactions for parallel compaction within a job to the number of hardware
|
||||
// threads, unless the value is explicitly provided in the config. The default is 1 (see
|
||||
// include/rocksdb/options.h in the Conan dependency directory), so don't use fewer
|
||||
// than that if no value is explicitly provided.
|
||||
if (keyValues.exists("max_subcompactions"))
|
||||
{
|
||||
m_options.max_subcompactions = get<unsigned int>(keyValues, "max_subcompactions");
|
||||
}
|
||||
else if (auto v = numHardwareThreads / 2; v > 1)
|
||||
{
|
||||
m_options.max_subcompactions = v;
|
||||
}
|
||||
|
||||
// Enable direct I/O by default unless explicitly disabled in the config. This bypasses the
|
||||
// OS page cache for better predictable performance on SSDs.
|
||||
m_options.use_direct_reads = get<bool>(keyValues, "use_direct_io", true);
|
||||
m_options.use_direct_io_for_flush_and_compaction =
|
||||
get<bool>(keyValues, "use_direct_io", true);
|
||||
|
||||
std::string s1, s2;
|
||||
rocksdb::GetStringFromDBOptions(&s1, m_options, "; ");
|
||||
rocksdb::GetStringFromColumnFamilyOptions(&s2, m_options, "; ");
|
||||
@@ -259,23 +295,19 @@ public:
|
||||
|
||||
rocksdb::ReadOptions const options;
|
||||
rocksdb::Slice const slice(std::bit_cast<char const*>(hash.data()), m_keyBytes);
|
||||
|
||||
std::string string;
|
||||
|
||||
rocksdb::Status getStatus = m_db->Get(options, slice, &string);
|
||||
|
||||
if (getStatus.ok())
|
||||
{
|
||||
DecodedBlob decoded(hash.data(), string.data(), string.size());
|
||||
|
||||
if (decoded.wasOk())
|
||||
{
|
||||
*pObject = decoded.createObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decoding failed, probably corrupted!
|
||||
//
|
||||
// Decoding failed, probably corrupted.
|
||||
status = dataCorrupt;
|
||||
}
|
||||
}
|
||||
@@ -292,7 +324,6 @@ public:
|
||||
else
|
||||
{
|
||||
status = Status(customCode + unsafe_cast<int>(getStatus.code()));
|
||||
|
||||
JLOG(m_journal.error()) << getStatus.ToString();
|
||||
}
|
||||
}
|
||||
@@ -303,19 +334,44 @@ public:
|
||||
std::pair<std::vector<std::shared_ptr<NodeObject>>, Status>
|
||||
fetchBatch(std::vector<uint256> const& hashes) override
|
||||
{
|
||||
std::vector<std::shared_ptr<NodeObject>> results;
|
||||
results.reserve(hashes.size());
|
||||
XRPL_ASSERT(m_db, "xrpl::NodeStore::RocksDBBackend::fetchBatch : non-null database");
|
||||
|
||||
if (hashes.empty())
|
||||
{
|
||||
return {{}, ok};
|
||||
}
|
||||
|
||||
// Use MultiGet for parallel reads to allow RocksDB to fetch multiple keys concurrently,
|
||||
// significantly improving throughput compared to sequential fetch() calls.
|
||||
|
||||
std::vector<rocksdb::Slice> keys;
|
||||
keys.reserve(hashes.size());
|
||||
for (auto const& h : hashes)
|
||||
{
|
||||
std::shared_ptr<NodeObject> nObj;
|
||||
Status status = fetch(h, &nObj);
|
||||
if (status != ok)
|
||||
keys.emplace_back(std::bit_cast<char const*>(h.data()), m_keyBytes);
|
||||
}
|
||||
|
||||
rocksdb::ReadOptions options;
|
||||
options.async_io = true; // Enable for better concurrency on supported platforms.
|
||||
std::vector<std::string> values(hashes.size());
|
||||
auto statuses = m_db->MultiGet(options, keys, &values);
|
||||
|
||||
std::vector<std::shared_ptr<NodeObject>> results(hashes.size());
|
||||
for (size_t i = 0; i < hashes.size(); ++i)
|
||||
{
|
||||
if (statuses[i].ok())
|
||||
{
|
||||
results.push_back({});
|
||||
DecodedBlob decoded(hashes[i].data(), values[i].data(), values[i].size());
|
||||
if (decoded.wasOk())
|
||||
{
|
||||
results[i] = decoded.createObject();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!statuses[i].IsNotFound())
|
||||
{
|
||||
results.push_back(nObj);
|
||||
// Log other errors but continue processing.
|
||||
JLOG(m_journal.warn()) << "fetchBatch: MultiGet error for key "
|
||||
<< keys[i].ToString() << ": " << statuses[i].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,25 +387,45 @@ public:
|
||||
void
|
||||
storeBatch(Batch const& batch) override
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
m_db,
|
||||
"xrpl::NodeStore::RocksDBBackend::storeBatch : non-null "
|
||||
"database");
|
||||
rocksdb::WriteBatch wb;
|
||||
XRPL_ASSERT(m_db, "xrpl::NodeStore::RocksDBBackend::storeBatch : non-null database");
|
||||
|
||||
if (batch.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
rocksdb::WriteBatch wb;
|
||||
for (auto const& e : batch)
|
||||
{
|
||||
EncodedBlob encoded(e);
|
||||
|
||||
wb.Put(
|
||||
rocksdb::Slice(std::bit_cast<char const*>(encoded.getKey()), m_keyBytes),
|
||||
rocksdb::Slice(std::bit_cast<char const*>(encoded.getData()), encoded.getSize()));
|
||||
}
|
||||
|
||||
rocksdb::WriteOptions const options;
|
||||
// Configure WriteOptions for high throughput.
|
||||
// Note: no_slowdown is intentionally NOT set here. When set to true, RocksDB returns an
|
||||
// error instead of stalling when write buffers are full, which could cause write
|
||||
// failures during high load. We prefer to accept brief stalls over dropped writes.
|
||||
rocksdb::WriteOptions options;
|
||||
|
||||
// Setting `sync = false` improves write throughput significantly by allowing the OS to
|
||||
// batch fsync operations, rather than forcing immediate disk synchronization on every
|
||||
// write. The Write-Ahead Log (WAL) is still written and flushed, so database consistency is
|
||||
// maintained across clean restarts and crashes.
|
||||
//
|
||||
// Note: On hard shutdown up to a few seconds of recent writes (since the last OS-initiated
|
||||
// flush) may be lost from this node. However, since ledger data is replicated across
|
||||
// the network, lost writes can be re-synced from peers during startup.
|
||||
options.sync = false;
|
||||
|
||||
// Keep WAL enabled for crash recovery consistency.
|
||||
options.disableWAL = false;
|
||||
|
||||
// Ensure RocksDB will not aggressive throttle the writes.
|
||||
options.low_pri = false;
|
||||
|
||||
auto ret = m_db->Write(options, &wb);
|
||||
|
||||
if (!ret.ok())
|
||||
Throw<std::runtime_error>("storeBatch failed: " + ret.ToString());
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
|
||||
//
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/paths/Flow.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -146,15 +146,14 @@ CredentialCreate::doApply()
|
||||
}
|
||||
else
|
||||
{
|
||||
// Added to both dirs, owned only by issuer. CredentialAccept will transfer ownership to
|
||||
// subject. CredentialDelete will remove from both dirs and decrement 1 ownerCount.
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(subject), credentialKey, describeOwnerDir(subject));
|
||||
JLOG(j_.trace()) << "Adding Credential to subject directory "
|
||||
<< to_string(credentialKey.key) << ": " << (page ? "success" : "failure");
|
||||
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfSubjectNode, *page);
|
||||
view().update(view().peek(keylet::account(subject)));
|
||||
}
|
||||
|
||||
view().insert(sleCred);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/tx/transactors/dex/PermissionedDEXHelpers.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <xrpl/conditions/Fulfillment.h>
|
||||
#include <xrpl/core/HashRouter.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h>
|
||||
//
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
|
||||
#include <xrpl/tx/transactors/payment/Payment.h>
|
||||
|
||||
@@ -98,7 +98,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
return !v || *v == (*sle)[field];
|
||||
};
|
||||
|
||||
std::int8_t adjustReserve = 0;
|
||||
std::uint32_t adjustReserve = 0;
|
||||
if (sle)
|
||||
{
|
||||
// update
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h>
|
||||
//
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/ledger/ApplyViewImpl.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
@@ -520,6 +520,13 @@ public:
|
||||
srcParams.set("type", srcBackendType);
|
||||
srcParams.set("path", node_db.path());
|
||||
|
||||
beast::temp_dir dest_db;
|
||||
Section destParams;
|
||||
destParams.set("type", destBackendType);
|
||||
destParams.set("path", dest_db.path());
|
||||
|
||||
testcase("import into '" + destBackendType + "' from '" + srcBackendType + "'");
|
||||
|
||||
// Create a batch
|
||||
auto batch = createPredictableBatch(numObjectsToTest, seedValue);
|
||||
|
||||
@@ -538,16 +545,9 @@ public:
|
||||
Manager::instance().make_Database(megabytes(4), scheduler, 2, srcParams, journal_);
|
||||
|
||||
// Set up the destination database
|
||||
beast::temp_dir dest_db;
|
||||
Section destParams;
|
||||
destParams.set("type", destBackendType);
|
||||
destParams.set("path", dest_db.path());
|
||||
|
||||
std::unique_ptr<Database> dest =
|
||||
Manager::instance().make_Database(megabytes(4), scheduler, 2, destParams, journal_);
|
||||
|
||||
testcase("import into '" + destBackendType + "' from '" + srcBackendType + "'");
|
||||
|
||||
// Do the import
|
||||
dest->importDatabase(*src);
|
||||
|
||||
|
||||
@@ -53,3 +53,7 @@ if(NOT WIN32)
|
||||
target_link_libraries(xrpl.test.net PRIVATE xrpl.imports.test)
|
||||
add_dependencies(xrpl.tests xrpl.test.net)
|
||||
endif()
|
||||
|
||||
xrpl_add_test(nodestore)
|
||||
target_link_libraries(xrpl.test.nodestore PRIVATE xrpl.imports.test)
|
||||
add_dependencies(xrpl.tests xrpl.test.nodestore)
|
||||
|
||||
334
src/tests/libxrpl/nodestore/BatchParallelism.cpp
Normal file
334
src/tests/libxrpl/nodestore/BatchParallelism.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include <xrpl/nodestore/Backend.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
using namespace xrpl;
|
||||
using namespace xrpl::NodeStore;
|
||||
|
||||
// Helper function to convert the pair result into ranges for testing.
|
||||
std::vector<std::pair<unsigned int, unsigned int>>
|
||||
calculateRanges(unsigned int batchSize, unsigned int maxThreadCount)
|
||||
{
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
|
||||
std::vector<std::pair<unsigned int, unsigned int>> ranges;
|
||||
ranges.reserve(numThreads);
|
||||
|
||||
for (unsigned int t = 0; t < numThreads; ++t)
|
||||
{
|
||||
auto const startIdx = t * numItems;
|
||||
auto const endIdx = std::min(startIdx + numItems, batchSize);
|
||||
ranges.emplace_back(startIdx, endIdx);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
TEST(BatchParallelism, EmptyBatch)
|
||||
{
|
||||
// Empty batch should return 0 threads.
|
||||
{
|
||||
auto const batchSize = 0u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 0u);
|
||||
EXPECT_EQ(numItems, 0u);
|
||||
|
||||
// Verify ranges calculation.
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(ranges.size(), numThreads);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BatchParallelism, SmallBatches)
|
||||
{
|
||||
// Batch size 1 should use 1 thread.
|
||||
{
|
||||
auto const batchSize = 1u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 1u);
|
||||
EXPECT_EQ(numItems, 1u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
EXPECT_EQ(ranges[0].first, 0u);
|
||||
EXPECT_EQ(ranges[0].second, 1u);
|
||||
}
|
||||
|
||||
// Batch size 2 should use 1 thread.
|
||||
{
|
||||
auto const batchSize = 2u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 1u);
|
||||
EXPECT_EQ(numItems, 2u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
EXPECT_EQ(ranges[0].first, 0u);
|
||||
EXPECT_EQ(ranges[0].second, 2u);
|
||||
}
|
||||
|
||||
// Batch size 3 should use 1 thread.
|
||||
{
|
||||
auto const batchSize = 3u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 1u);
|
||||
EXPECT_EQ(numItems, 3u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
EXPECT_EQ(ranges[0].first, 0u);
|
||||
EXPECT_EQ(ranges[0].second, 3u);
|
||||
}
|
||||
|
||||
// Batch size 4 should use 1 thread (exactly 4 items).
|
||||
{
|
||||
auto const batchSize = 4u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 1u);
|
||||
EXPECT_EQ(numItems, 4u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
EXPECT_EQ(ranges[0].first, 0u);
|
||||
EXPECT_EQ(ranges[0].second, 4u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BatchParallelism, MediumBatches)
|
||||
{
|
||||
// Batch size 5 should use 2 threads.
|
||||
{
|
||||
auto const batchSize = 5u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 2u); // ceil(5/4) = 2
|
||||
EXPECT_EQ(numItems, 3u); // ceil(5/2) = 3
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
EXPECT_EQ(ranges[0].first, 0u);
|
||||
EXPECT_EQ(ranges[0].second, 3u);
|
||||
EXPECT_EQ(ranges[1].first, 3u);
|
||||
EXPECT_EQ(ranges[1].second, 5u);
|
||||
}
|
||||
|
||||
// Batch size 8 should use 2 threads.
|
||||
{
|
||||
auto const batchSize = 8u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 2u);
|
||||
EXPECT_EQ(numItems, 4u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
}
|
||||
|
||||
// Batch size 15 should use 4 threads (ceil(15/4) = 4).
|
||||
{
|
||||
auto const batchSize = 15u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 4u);
|
||||
EXPECT_EQ(numItems, 4u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads - 1; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
EXPECT_EQ(ranges[numThreads - 1].first, (numThreads - 1) * numItems);
|
||||
EXPECT_EQ(ranges[numThreads - 1].second, batchSize); // Last range gets remaining items.
|
||||
}
|
||||
|
||||
// Batch size 22 should use 6 threads.
|
||||
{
|
||||
auto const batchSize = 22u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 6u); // ceil(22/4) = 6
|
||||
EXPECT_EQ(numItems, 4u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads - 1; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
EXPECT_EQ(ranges[numThreads - 1].first, (numThreads - 1) * numItems);
|
||||
EXPECT_EQ(ranges[numThreads - 1].second, batchSize);
|
||||
}
|
||||
|
||||
// Batch size 32 should use 8 threads.
|
||||
{
|
||||
auto const batchSize = 32u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 8u);
|
||||
EXPECT_EQ(numItems, 4u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BatchParallelism, LargeBatches)
|
||||
{
|
||||
// Batch size 100 should use 8 threads (max limit).
|
||||
{
|
||||
auto const batchSize = 100u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 8u);
|
||||
EXPECT_EQ(numItems, 13u); // ceil(100/8) = 13
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads - 1; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
EXPECT_EQ(ranges[numThreads - 1].first, (numThreads - 1) * numItems);
|
||||
EXPECT_EQ(ranges[numThreads - 1].second, batchSize);
|
||||
}
|
||||
|
||||
// Batch size 1000 with 8 hw threads.
|
||||
{
|
||||
auto const batchSize = 1000u;
|
||||
auto const maxThreadCount = 8u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 8u);
|
||||
EXPECT_EQ(numItems, 125u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BatchParallelism, HardwareThreadLimits)
|
||||
{
|
||||
// With only 1 thread available.
|
||||
{
|
||||
auto const batchSize = 100u;
|
||||
auto const maxThreadCount = 1u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 1u);
|
||||
EXPECT_EQ(numItems, 100u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
EXPECT_EQ(ranges[0].first, 0u);
|
||||
EXPECT_EQ(ranges[0].second, 100u);
|
||||
}
|
||||
|
||||
// With 2 threads.
|
||||
{
|
||||
auto const batchSize = 50u;
|
||||
auto const maxThreadCount = 2u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 2u);
|
||||
EXPECT_EQ(numItems, 25u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
}
|
||||
|
||||
// With 10 threads.
|
||||
{
|
||||
auto const batchSize = 50u;
|
||||
auto const maxThreadCount = 12u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 10u); // ceil(50/4) = 13, but numThreads = 10.
|
||||
EXPECT_EQ(numItems, 5u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
}
|
||||
|
||||
// With many threads.
|
||||
{
|
||||
auto const batchSize = 20u;
|
||||
auto const maxThreadCount = 100u;
|
||||
|
||||
auto const [numThreads, numItems] =
|
||||
Backend::calculateBatchParallelism(batchSize, maxThreadCount);
|
||||
EXPECT_EQ(numThreads, 5u); // ceil(20/4) = 5, limited by batch size.
|
||||
EXPECT_EQ(numItems, 4u);
|
||||
|
||||
auto const ranges = calculateRanges(batchSize, maxThreadCount);
|
||||
ASSERT_EQ(ranges.size(), numThreads);
|
||||
for (size_t i = 0; i < numThreads; ++i)
|
||||
{
|
||||
EXPECT_EQ(ranges[i].first, i * numItems);
|
||||
EXPECT_EQ(ranges[i].second, (i + 1) * numItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/tests/libxrpl/nodestore/main.cpp
Normal file
8
src/tests/libxrpl/nodestore/main.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <xrpld/app/paths/detail/StepChecks.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <xrpld/app/paths/detail/StepChecks.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
|
||||
@@ -218,12 +218,6 @@ doGetAggregatePrice(RPC::JsonContext& context)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the ledger
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
result = RPC::lookupLedger(ledger, context);
|
||||
if (!ledger)
|
||||
return result; // LCOV_EXCL_LINE
|
||||
|
||||
// Collect the dataset into bimap keyed by lastUpdateTime and
|
||||
// STAmount (Number is int64 and price is uint64)
|
||||
Prices prices;
|
||||
@@ -244,6 +238,11 @@ doGetAggregatePrice(RPC::JsonContext& context)
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
result = RPC::lookupLedger(ledger, context);
|
||||
if (!ledger)
|
||||
return result; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sle = ledger->read(keylet::oracle(*account, *documentID));
|
||||
iteratePriceData(context, sle, [&](STObject const& node) {
|
||||
auto const& series = node.getFieldArray(sfPriceDataSeries);
|
||||
@@ -285,8 +284,8 @@ doGetAggregatePrice(RPC::JsonContext& context)
|
||||
if (auto const threshold = std::get<std::uint32_t>(timeThreshold))
|
||||
{
|
||||
// threshold defines an acceptable range {max,min} of lastUpdateTime as
|
||||
// {latestTime, latestTime - threshold}. Prices with lastUpdateTime
|
||||
// less than (latestTime - threshold) are erased (outdated prices).
|
||||
// {latestTime, latestTime - threshold}, the prices with lastUpdateTime
|
||||
// greater than (latestTime - threshold) are erased.
|
||||
auto const oldestTime = prices.left.rbegin()->first;
|
||||
auto const upperBound = latestTime > threshold ? (latestTime - threshold) : oldestTime;
|
||||
if (upperBound > oldestTime)
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/core/LexicalCast.h>
|
||||
#include <xrpl/json/json_errors.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
|
||||
Reference in New Issue
Block a user