Merge branch 'develop' into ximinez/fix-getledger

This commit is contained in:
Ed Hennis
2026-05-13 22:31:59 -04:00
committed by GitHub
21 changed files with 479 additions and 151 deletions

101
.github/workflows/build-nix-image.yml vendored Normal file
View File

@@ -0,0 +1,101 @@
name: Build Nix Docker image
on:
push:
branches:
- develop
paths:
- ".github/workflows/build-nix-image.yml"
- "docker/nix.Dockerfile"
- "flake.nix"
- "flake.lock"
- "nix/**"
pull_request:
paths:
- ".github/workflows/build-nix-image.yml"
- "docker/nix.Dockerfile"
- "flake.nix"
- "flake.lock"
- "nix/**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
UBUNTU_VERSION: "20.04"
RHEL_VERSION: "9"
DEBIAN_VERSION: "bookworm"
jobs:
build:
name: Build and push Nix image (${{ matrix.distro }})
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
include:
- distro: nixos
- distro: ubuntu
- distro: rhel
- distro: debian
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Determine base image
id: vars
run: |
case "${{ matrix.distro }}" in
nixos)
echo "base_image=nixos/nix:latest" >> $GITHUB_OUTPUT
;;
ubuntu)
echo "base_image=ubuntu:${UBUNTU_VERSION}" >> $GITHUB_OUTPUT
;;
rhel)
echo "base_image=registry.access.redhat.com/ubi${RHEL_VERSION}/ubi:latest" >> $GITHUB_OUTPUT
;;
debian)
echo "base_image=debian:${DEBIAN_VERSION}" >> $GITHUB_OUTPUT
;;
esac
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Login to GitHub Container Registry
if: github.event_name == 'push'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: ghcr.io/xrplf/ci/nix-${{ matrix.distro }}
tags: |
type=sha,prefix=sha-,format=short
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
file: docker/nix.Dockerfile
platforms: linux/amd64
push: ${{ github.event_name == 'push' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: BASE_IMAGE=${{ steps.vars.outputs.base_image }}

View File

@@ -70,7 +70,11 @@ repos:
rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0
hooks:
- id: cspell # Spell check changed files
exclude: (.config/cspell.config.yaml|^include/xrpl/protocol_autogen/(transactions|ledger_entries)/)
exclude: |
(?x)^(
.config/cspell.config.yaml|
include/xrpl/protocol_autogen/(transactions|ledger_entries)/.*
)$
- id: cspell # Spell check the commit message
name: check commit message spelling
args:

View File

@@ -3,3 +3,5 @@
core:non_interactive=True
core.download:parallel={{ os.cpu_count() }}
core.upload:parallel={{ os.cpu_count() }}
tools.files.download:retry=5
tools.files.download:retry_wait=10

View File

@@ -63,6 +63,7 @@ words:
- Bougalis
- Britto
- Btrfs
- Buildx
- canonicality
- changespq
- checkme

66
docker/nix.Dockerfile Normal file
View File

@@ -0,0 +1,66 @@
ARG BASE_IMAGE=nixos/nix:latest
# Nix builder
FROM nixos/nix:latest AS builder-source
RUN mkdir -p ~/.config/nix && \
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
# Copy our source and setup our working dir.
COPY nix/ci-env.nix /tmp/build/nix/ci-env.nix
COPY nix/packages.nix /tmp/build/nix/packages.nix
COPY nix/utils.nix /tmp/build/nix/utils.nix
COPY flake.nix /tmp/build/
COPY flake.lock /tmp/build/
WORKDIR /tmp/build
FROM builder-source AS builder
# Build our Nix CI environment (all build tools in a single store path)
RUN nix \
--option filter-syscalls false \
build
# Copy the Nix store closure into a directory. The Nix store closure is the
# entire set of Nix store values that we need for our build.
RUN mkdir /tmp/nix-store-closure && \
cp -R $(nix-store -qR result/) /tmp/nix-store-closure
# Final image
FROM ${BASE_IMAGE}
# bash is not located at /bin/bash in nixos/nix, so we need to create a symlink to it.
RUN if [ -d /nix ]; then \
ln -s /root/.nix-profile/bin/bash /bin/bash; \
fi
# Use Bash as the default shell for RUN commands, using the options
# `set -o errexit -o pipefail`, and as the entrypoint.
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
ENTRYPOINT ["/bin/bash"]
# Copy /nix/store and the env symlink tree
COPY --from=builder /tmp/nix-store-closure /nix/store
COPY --from=builder /tmp/build/result /nix/ci-env
ENV PATH="/nix/ci-env/bin:$PATH"
RUN <<EOF
ccache --version
clang-format --version
cmake --version
conan --version
g++ --version
gcc --version
gcovr --version
git --version
make --version
mold --version
ninja --version
perl --version
pkg-config --version
pre-commit --version
python3 --version
run-clang-tidy --help
vim --version
EOF

26
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1769461804,
"narHash": "sha256-6h5sROT/3CTHvzPy9koKBmoCa2eJKh4fzQK8eYFEgl8=",
"lastModified": 1777954456,
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b579d443b37c9c5373044201ea77604e37e748c8",
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
"type": "github"
},
"original": {
@@ -15,9 +15,27 @@
"type": "indirect"
}
},
"nixpkgs-glibc231": {
"flake": false,
"locked": {
"lastModified": 1593520194,
"narHash": "sha256-+TZW+2I7kLL9JglPNOagm1ywjf9ua0JYGoptq/dzVn0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9cd98386a38891d1074fc18036b842dc4416f562",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9cd98386a38891d1074fc18036b842dc4416f562",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"nixpkgs-glibc231": "nixpkgs-glibc231"
}
}
},

View File

@@ -2,15 +2,24 @@
description = "Nix related things for xrpld";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
# nixpkgs snapshot (2020-06-30) that shipped glibc 2.31 as the primary
# version — matches the system libc on Ubuntu 20.04 LTS. Imported
# manually (flake = false) because this revision predates nixpkgs'
# own flake.nix.
nixpkgs-glibc231 = {
url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562";
flake = false;
};
};
outputs =
{ nixpkgs, ... }:
{ nixpkgs, nixpkgs-glibc231, ... }:
let
forEachSystem = (import ./nix/utils.nix { inherit nixpkgs; }).forEachSystem;
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-glibc231; };
in
{
devShells = forEachSystem (import ./nix/devshell.nix);
packages = forEachSystem (import ./nix/ci-env.nix);
formatter = forEachSystem ({ pkgs, ... }: pkgs.nixfmt);
};
}

View File

@@ -148,17 +148,23 @@ public:
}
[[nodiscard]] constexpr E const&
error() const
error() const&
{
return Base::error();
}
constexpr E&
error()
[[nodiscard]] constexpr E&
error() &
{
return Base::error();
}
[[nodiscard]] constexpr E&&
error() &&
{
return std::move(Base::error());
}
constexpr explicit
operator bool() const
{
@@ -215,17 +221,23 @@ public:
}
[[nodiscard]] constexpr E const&
error() const
error() const&
{
return Base::error();
}
constexpr E&
error()
[[nodiscard]] constexpr E&
error() &
{
return Base::error();
}
[[nodiscard]] constexpr E&&
error() &&
{
return std::move(Base::error());
}
constexpr explicit
operator bool() const
{

View File

@@ -7,9 +7,11 @@
#include <boost/utility/string_view.hpp>
#include <array>
#include <concepts>
#include <cstdint>
#include <optional>
#include <string>
#include <type_traits>
namespace xrpl {
@@ -26,28 +28,39 @@ namespace xrpl {
std::string
sqlBlobLiteral(Blob const& blob);
namespace detail {
template <typename T>
concept SomeChar = std::same_as<std::remove_cvref_t<T>, int8_t> ||
std::same_as<std::remove_cvref_t<T>, char> || std::same_as<std::remove_cvref_t<T>, uint8_t>;
inline constexpr std::array<std::optional<int>, 256> const kDIGIT_LOOKUP_TABLE = []() {
std::array<std::optional<int>, 256> t{};
for (int i = 0; i < 10; ++i)
t['0' + i] = i;
for (int i = 0; i < 6; ++i)
{
t['A' + i] = 10 + i;
t['a' + i] = 10 + i;
}
return t;
}();
inline std::optional<int>
hexCharToInt(SomeChar auto hexChar)
{
return kDIGIT_LOOKUP_TABLE[static_cast<uint8_t>(hexChar)];
}
} // namespace detail
template <class Iterator>
std::optional<Blob>
strUnHex(std::size_t strSize, Iterator begin, Iterator end)
{
static constexpr std::array<int, 256> const kDIGIT_LOOKUP_TABLE = []() {
std::array<int, 256> t{};
for (auto& x : t)
x = -1;
for (int i = 0; i < 10; ++i)
t['0' + i] = i;
for (int i = 0; i < 6; ++i)
{
t['A' + i] = 10 + i;
t['a' + i] = 10 + i;
}
return t;
}();
Blob out;
out.reserve((strSize + 1) / 2);
@@ -56,27 +69,26 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
if (strSize & 1)
{
int c = kDIGIT_LOOKUP_TABLE[*iter++];
if (c < 0)
auto const c = detail::hexCharToInt(*iter++);
if (!c.has_value())
return {};
out.push_back(c);
out.push_back(static_cast<unsigned char>(*c));
}
while (iter != end)
{
int const cHigh = kDIGIT_LOOKUP_TABLE[*iter++];
auto const cHigh = detail::hexCharToInt(*iter++);
if (cHigh < 0)
if (!cHigh.has_value())
return {};
int const cLow = kDIGIT_LOOKUP_TABLE[*iter++];
auto const cLow = detail::hexCharToInt(*iter++);
if (cLow < 0)
if (!cLow.has_value())
return {};
out.push_back(static_cast<unsigned char>((cHigh << 4) | cLow));
out.push_back(static_cast<unsigned char>((*cHigh << 4) | *cLow));
}
return {std::move(out)};

View File

@@ -15,11 +15,10 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (Cleanup3_2_0, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
XRPL_FIX (PermissionedDomainInvariant, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::Yes, VoteBehavior::DefaultNo)
@@ -34,7 +33,7 @@ XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::Yes, VoteBehavior::DefaultN
XRPL_FIX (AMMv1_3, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDEX, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::Yes, VoteBehavior::DefaultNo)
// Check flags in Credential transactions
XRPL_FIX (InvalidTxFlags, Supported::Yes, VoteBehavior::DefaultNo)

54
nix/ci-env.nix Normal file
View File

@@ -0,0 +1,54 @@
{
pkgs,
glibc231,
...
}:
let
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
# binutils wrapped to emit binaries that reference glibc 2.31 (dynamic
# linker path, library search path, RPATH).
binutils231 = pkgs.wrapBintoolsWith {
bintools = pkgs.binutils-unwrapped;
libc = glibc231;
};
# Rebuild gcc 15 (specifically libstdc++ / libgcc_s) against glibc 2.31.
# The override swaps gcc15.cc's bootstrap stdenv for one that uses the
# existing gcc 15 binary but links against glibc 2.31, so the resulting
# compiler ships runtime libraries that only reference symbols available
# in glibc 2.31.
gcc15CcWithGlibc231 = pkgs.gcc15.cc.override {
stdenv = pkgs.stdenvAdapters.overrideCC pkgs.stdenv (
pkgs.wrapCCWith {
cc = pkgs.gcc15.cc;
libc = glibc231;
bintools = binutils231;
}
);
};
# cc-wrapper around the rebuilt compiler, pointing at glibc 2.31 headers
# and libraries. This is what we actually expose to users.
gcc15WithGlibc231 = pkgs.wrapCCWith {
cc = gcc15CcWithGlibc231;
libc = glibc231;
bintools = binutils231;
};
in
{
default = pkgs.buildEnv {
name = "xrpld-ci-env";
paths = commonPackages ++ [
gcc15WithGlibc231
binutils231
];
pathsToLink = [
"/bin"
"/lib"
"/include"
"/share"
];
};
}

View File

@@ -1,19 +1,6 @@
{ pkgs, ... }:
let
commonPackages = with pkgs; [
ccache
cmake
conan
gcovr
git
gnumake
llvmPackages_21.clang-tools
ninja
perl # needed for openssl
pkg-config
pre-commit
python314
];
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
# Supported compiler versions
gccVersion = pkgs.lib.range 13 15;

27
nix/packages.nix Normal file
View File

@@ -0,0 +1,27 @@
{ pkgs }:
let
# In LLVM 22, run-clang-tidy.py moved from share/clang/ to bin/, so nixpkgs
# clang-tools no longer links it. Wrap it manually.
runClangTidy = pkgs.writeShellScriptBin "run-clang-tidy" ''
exec ${pkgs.python3}/bin/python3 ${pkgs.llvmPackages_22.clang-unwrapped}/bin/run-clang-tidy "$@"
'';
in
{
commonPackages = with pkgs; [
ccache
cmake
conan
gcovr
git
gnumake
llvmPackages_22.clang-tools
mold
ninja
perl # needed for openssl
pkg-config
pre-commit
python3
runClangTidy
vim
];
}

View File

@@ -1,19 +1,21 @@
{ nixpkgs }:
{
forEachSystem =
function:
nixpkgs.lib.genAttrs
[
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
]
(
system:
function {
inherit system;
pkgs = import nixpkgs { inherit system; };
}
);
}
{ nixpkgs, nixpkgs-glibc231 }:
function:
nixpkgs.lib.genAttrs
[
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
]
(
system:
function {
pkgs = import nixpkgs { inherit system; };
# glibc 2.31 — matches the system libc on Ubuntu 20.04 LTS. Sourced
# from the nixpkgs snapshot pinned via the `nixpkgs-glibc231` flake
# input, so the build uses the compiler from that snapshot
# (gcc 9.3.0) along with the matching patches, configure flags, and
# hardening defaults.
glibc231 = (import nixpkgs-glibc231 { inherit system; }).glibc;
}
)

View File

@@ -102,7 +102,7 @@ ValidPermissionedDomain::finalize(
return true;
};
if (view.rules().enabled(fixPermissionedDomainInvariant))
if (view.rules().enabled(fixCleanup3_1_3))
{
// No permissioned domains should be affected if the transaction failed
if (!isTesSuccess(result))

View File

@@ -110,8 +110,8 @@ PermissionedDomainSet::doApply()
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
bool const fix313 = view().rules().enabled(fixCleanup3_1_3);
auto const seq = fix313 ? ctx_.tx.getSeqValue() : ctx_.tx.getFieldU32(sfSequence);
bool const fixEnabled = view().rules().enabled(fixCleanup3_1_3);
auto const seq = fixEnabled ? ctx_.tx.getSeqValue() : ctx_.tx.getFieldU32(sfSequence);
Keylet const pdKeylet = keylet::permissionedDomain(account_, seq);
auto slePd = std::make_shared<SLE>(pdKeylet);

View File

@@ -1291,8 +1291,8 @@ class Invariants_test : public beast::unit_test::Suite
if (numCreds != 0u)
{
// This array is sorted naturally, but if you willing to change this
// behavior don't forget to use credentials::makeSorted
// This array is sorted naturally, but if you are going to change
// this behavior, don't forget to use credentials::makeSorted
STArray credentials(sfAcceptedCredentials, numCreds);
for (std::size_t n = 0; n < numCreds; ++n)
{
@@ -1314,11 +1314,11 @@ class Invariants_test : public beast::unit_test::Suite
{
using namespace test::jtx;
bool const fixPDEnabled = features[fixPermissionedDomainInvariant];
bool const fixEnabled = features[fixCleanup3_1_3];
std::initializer_list<TER> const badTers = {tecINVARIANT_FAILED, tecINVARIANT_FAILED};
std::initializer_list<TER> const failTers = {tecINVARIANT_FAILED, tefINVARIANT_FAILED};
testcase << "PermissionedDomain" + std::string(fixPDEnabled ? " fix" : "");
testcase << "PermissionedDomain" + std::string(fixEnabled ? " fix" : "");
doInvariantCheck(
Env(*this, features),
@@ -1328,7 +1328,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
testcase << "PermissionedDomain 2";
@@ -1341,7 +1341,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
testcase << "PermissionedDomain 3";
doInvariantCheck(
@@ -1365,7 +1365,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
testcase << "PermissionedDomain 4";
doInvariantCheck(
@@ -1388,7 +1388,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
testcase << "PermissionedDomain Set 1";
doInvariantCheck(
@@ -1409,7 +1409,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
testcase << "PermissionedDomain Set 2";
doInvariantCheck(
@@ -1440,7 +1440,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
testcase << "PermissionedDomain Set 3";
doInvariantCheck(
@@ -1470,7 +1470,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
testcase << "PermissionedDomain Set 4";
doInvariantCheck(
@@ -1498,7 +1498,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : badTers);
fixEnabled ? failTers : badTers);
std::initializer_list<TER> const goodTers = {tesSUCCESS, tesSUCCESS};
@@ -1516,7 +1516,7 @@ class Invariants_test : public beast::unit_test::Suite
testcase << "PermissionedDomain set 2 domains ";
doInvariantCheck(
Env(*this, features),
fixPDEnabled ? badMoreThan1 : emptyV,
fixEnabled ? badMoreThan1 : emptyV,
[](Account const& a1, Account const& a2, ApplyContext& ac) {
createPermissionedDomain(ac, a1, a2);
createPermissionedDomain(ac, a1, a2, 2, 11);
@@ -1524,7 +1524,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : goodTers);
fixEnabled ? failTers : goodTers);
}
{
@@ -1545,7 +1545,7 @@ class Invariants_test : public beast::unit_test::Suite
std::move(env1),
a1,
a2,
fixPDEnabled ? badMoreThan1 : emptyV,
fixEnabled ? badMoreThan1 : emptyV,
[&pd1, &pd2](Account const&, Account const&, ApplyContext& ac) {
auto sle1 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd1});
auto sle2 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd2});
@@ -1555,18 +1555,18 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
fixPDEnabled ? failTers : goodTers);
fixEnabled ? failTers : goodTers);
}
{
testcase << "PermissionedDomain set 0 domains ";
doInvariantCheck(
Env(*this, features),
fixPDEnabled ? badNoDomains : emptyV,
fixEnabled ? badNoDomains : emptyV,
[](Account const&, Account const&, ApplyContext&) { return true; },
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? badTers : goodTers);
fixEnabled ? badTers : goodTers);
}
{
@@ -1587,11 +1587,11 @@ class Invariants_test : public beast::unit_test::Suite
Env(*this, features),
a1,
a2,
fixPDEnabled ? badNoDomains : emptyV,
fixEnabled ? badNoDomains : emptyV,
[](Account const&, Account const&, ApplyContext&) { return true; },
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
fixPDEnabled ? badTers : goodTers);
fixEnabled ? badTers : goodTers);
}
{
@@ -1611,7 +1611,7 @@ class Invariants_test : public beast::unit_test::Suite
std::move(env1),
a1,
a2,
fixPDEnabled ? badDeleted : emptyV,
fixEnabled ? badDeleted : emptyV,
[&pd1](Account const&, Account const&, ApplyContext& ac) {
auto sle1 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd1});
ac.view().erase(sle1);
@@ -1619,28 +1619,28 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
fixPDEnabled ? failTers : goodTers);
fixEnabled ? failTers : goodTers);
}
{
testcase << "PermissionedDomain del, create domain ";
doInvariantCheck(
Env(*this, features),
fixPDEnabled ? badNotDeleted : emptyV,
fixEnabled ? badNotDeleted : emptyV,
[](Account const& a1, Account const& a2, ApplyContext& ac) {
createPermissionedDomain(ac, a1, a2);
return true;
},
XRPAmount{},
STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
fixPDEnabled ? failTers : goodTers);
fixEnabled ? failTers : goodTers);
}
{
testcase << "PermissionedDomain invalid tx";
doInvariantCheck(
fixPDEnabled ? badTx : emptyV,
fixEnabled ? badTx : emptyV,
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
createPermissionedDomain(ac, a1, a2);
return true;
@@ -1800,11 +1800,9 @@ class Invariants_test : public beast::unit_test::Suite
{
using namespace test::jtx;
bool const fixPDEnabled = features[fixPermissionedDomainInvariant];
bool const fixS313Enabled = features[fixCleanup3_1_3];
bool const fixEnabled = features[fixCleanup3_1_3];
testcase << "PermissionedDEX" + std::string(fixPDEnabled ? " fixPD" : "") +
std::string(fixS313Enabled ? " fixS313" : "");
testcase << "PermissionedDEX" + std::string(fixEnabled ? " fix" : "");
doInvariantCheck(
Env(*this, features),
@@ -1908,8 +1906,8 @@ class Invariants_test : public beast::unit_test::Suite
std::move(env1),
a1,
a2,
fixS313Enabled ? std::vector<std::string>{{"hybrid offer is malformed"}}
: std::vector<std::string>{},
fixEnabled ? std::vector<std::string>{{"hybrid offer is malformed"}}
: std::vector<std::string>{},
[&pd1](Account const& a1, Account const& a2, ApplyContext& ac) {
Keylet const offerKey = keylet::offer(a2.id(), 10);
auto sleOffer = std::make_shared<SLE>(offerKey);
@@ -1926,9 +1924,8 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttOFFER_CREATE, [&](STObject&) {}},
fixS313Enabled
? std::initializer_list<TER>{tecINVARIANT_FAILED, tecINVARIANT_FAILED}
: std::initializer_list<TER>{tesSUCCESS, tesSUCCESS});
fixEnabled ? std::initializer_list<TER>{tecINVARIANT_FAILED, tecINVARIANT_FAILED}
: std::initializer_list<TER>{tesSUCCESS, tesSUCCESS});
}
// hybrid offer missing sfAdditionalBooks
@@ -4380,13 +4377,10 @@ public:
testNoZeroEscrow();
testValidNewAccountRoot();
testNFTokenPageInvariants();
testPermissionedDomainInvariants(defaultAmendments() | fixPermissionedDomainInvariant);
testPermissionedDomainInvariants(defaultAmendments() - fixPermissionedDomainInvariant);
testPermissionedDEX(defaultAmendments() | fixPermissionedDomainInvariant);
testPermissionedDEX(defaultAmendments() - fixPermissionedDomainInvariant);
testPermissionedDEX(
(defaultAmendments() | fixPermissionedDomainInvariant) - fixCleanup3_1_3);
testPermissionedDEX(defaultAmendments() - fixPermissionedDomainInvariant - fixCleanup3_1_3);
testPermissionedDomainInvariants(defaultAmendments() | fixCleanup3_1_3);
testPermissionedDomainInvariants(defaultAmendments() - fixCleanup3_1_3);
testPermissionedDEX(defaultAmendments() | fixCleanup3_1_3);
testPermissionedDEX(defaultAmendments() - fixCleanup3_1_3);
testNoModifiedUnmodifiableFields();
testValidPseudoAccounts();
testValidLoanBroker();

View File

@@ -1392,10 +1392,10 @@ class PermissionedDEX_test : public beast::unit_test::Suite
void
testHybridMalformedOffer(FeatureBitset features)
{
bool const fixS313Enabled = features[fixCleanup3_1_3];
bool const fixEnabled = features[fixCleanup3_1_3];
testcase << "Hybrid offer with empty AdditionalBooks"
<< (fixS313Enabled ? " (fixCleanup3_1_3 enabled)" : " (fixCleanup3_1_3 disabled)");
<< (fixEnabled ? " (fixCleanup3_1_3 enabled)" : " (fixCleanup3_1_3 disabled)");
// offerInDomain has two code paths gated by fixCleanup3_1_3:
//
@@ -1436,7 +1436,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite
return true;
});
if (fixS313Enabled)
if (fixEnabled)
{
// post-fixCleanup3_1_3: offerInDomain rejects the malformed
// offer (size == 0), so no valid domain offer is found.

View File

@@ -49,14 +49,10 @@ exceptionExpected(Env& env, json::Value const& jv)
class PermissionedDomains_test : public beast::unit_test::Suite
{
FeatureBitset withoutFeature_{testableAmendments() - featurePermissionedDomains};
FeatureBitset withFeature_{
testableAmendments() //
| featurePermissionedDomains | featureCredentials};
(testableAmendments() | featurePermissionedDomains | featureCredentials) - fixCleanup3_1_3};
FeatureBitset withFix_{
testableAmendments() //
| featurePermissionedDomains | featureCredentials};
testableAmendments() | featurePermissionedDomains | featureCredentials | fixCleanup3_1_3};
// Verify that each tx type can execute if the feature is enabled.
void
@@ -98,7 +94,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite
{
testcase("Disabled");
Account const alice("alice");
Env env(*this, withoutFeature_);
Env env(*this, testableAmendments() - featurePermissionedDomains);
env.fund(XRP(1000), alice);
pdomain::Credentials const credentials{{alice, "first credential"}};
env(pdomain::setTx(alice, credentials), Ter(temDISABLED));

View File

@@ -18,6 +18,7 @@
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <iterator>
#include <string_view>
#include <vector>
@@ -252,32 +253,46 @@ forwardedFor(http_request_type const& request)
// Look for the Forwarded field in the request.
if (auto it = request.find(boost::beast::http::field::forwarded); it != request.end())
{
auto asciiTolower = [](char c) -> char {
auto asciiToLower = [](char c) -> char {
return ((static_cast<unsigned>(c) - 65U) < 26) ? c + 'a' - 'A' : c;
};
// Look for the first (case insensitive) "for="
static std::string const kFOR_STR{"for="};
char const* found = std::search(
it->value().begin(),
it->value().end(),
kFOR_STR.begin(),
kFOR_STR.end(),
[&asciiTolower](char c1, char c2) { return asciiTolower(c1) == asciiTolower(c2); });
// Look for the first (case insensitive) "for=" at a directive
// boundary (start of value, or preceded by , ; or OWS).
static constexpr std::string_view kFOR_STR{"for="};
auto const atFieldBoundary = [begin = it->value().begin()](auto p) {
return p == begin || p[-1] == ';' || p[-1] == ',' || p[-1] == ' ' || p[-1] == '\t';
};
auto found = it->value().begin();
while (true)
{
found = std::search(
found,
it->value().end(),
kFOR_STR.begin(),
kFOR_STR.end(),
[&asciiToLower](char c1, char c2) { return asciiToLower(c1) == asciiToLower(c2); });
if (found == it->value().end())
return {};
if (found == it->value().end())
return {};
found += kFOR_STR.size();
if (atFieldBoundary(found))
break;
++found;
}
std::advance(found, kFOR_STR.size());
// We found a "for=". Scan for the end of the IP address.
std::size_t const pos = [&found, &it]() {
auto const remaining = static_cast<std::size_t>(it->value().end() - found);
if (std::size_t const pos = std::string_view(found, remaining).find_first_of(",;");
pos != std::string_view::npos)
auto const end = it->value().end();
std::size_t const pos = [&found, &end]() {
std::size_t const pos =
std::string_view(found, std::distance(found, end)).find_first_of(",;");
if (pos != std::string_view::npos)
return pos;
return remaining;
return static_cast<std::size_t>(std::distance(found, end));
}();
return extractIpAddrFromField({found, pos});

View File

@@ -14,6 +14,7 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Expected.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/Slice.h>
@@ -54,6 +55,7 @@
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <exception>
#include <functional>
#include <memory>
@@ -405,6 +407,25 @@ checkTxJsonFields(
return ret;
}
static Expected<void, json::Value>
checkNetworkID(json::Value const& txJson, uint32_t appNetworkId)
{
if (appNetworkId > 1024)
{
if (!txJson.isMember(jss::NetworkID))
{
return Unexpected(
RPC::makeError(RpcInvalidParams, RPC::missingFieldMessage("tx_json.NetworkID")));
}
if (!txJson[jss::NetworkID].isIntegral() || txJson[jss::NetworkID].asUInt() != appNetworkId)
{
return Unexpected(
RPC::makeError(RpcInvalidParams, RPC::invalidFieldMessage("tx_json.NetworkID")));
}
}
return Expected<void, json::Value>();
}
//------------------------------------------------------------------------------
// A move-only struct that makes it easy to return either a json::Value or a
@@ -1165,8 +1186,16 @@ transactionSignFor(
if (!txJson.isObject())
return RPC::objectFieldError(jss::tx_json);
// If the tx_json.SigningPubKey field is missing,
// insert an empty one.
if (auto checkResult =
detail::checkNetworkID(txJson, app.getNetworkIDService().getNetworkID());
!checkResult)
{
return std::move(checkResult).error();
}
// If the tx_json.SigningPubKey field is missing, insert an empty one,
// in order for the `checkMultiSignFields` to not return an error
// for non-multisign transactions.
if (!txJson.isMember(sfSigningPubKey.getJsonName()))
txJson[sfSigningPubKey.getJsonName()] = "";
}