mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 17:27:00 +00:00
Compare commits
16 Commits
mvadari/rp
...
gregtatcam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
845b7ef7b5 | ||
|
|
54f8450baf | ||
|
|
61f51e5445 | ||
|
|
1b8776ed36 | ||
|
|
afbccf971a | ||
|
|
2f65cb5610 | ||
|
|
d4ebd6a168 | ||
|
|
551f3c3b96 | ||
|
|
aa5e4ff89f | ||
|
|
977e5a7dba | ||
|
|
648ec747f2 | ||
|
|
17ee7e784c | ||
|
|
a87cff1c1b | ||
|
|
c33526f88d | ||
|
|
e59cb667e6 | ||
|
|
4b2d7871fb |
101
.github/workflows/build-nix-image.yml
vendored
Normal file
101
.github/workflows/build-nix-image.yml
vendored
Normal 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 }}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -63,6 +63,7 @@ words:
|
||||
- Bougalis
|
||||
- Britto
|
||||
- Btrfs
|
||||
- Buildx
|
||||
- canonicality
|
||||
- changespq
|
||||
- checkme
|
||||
|
||||
66
docker/nix.Dockerfile
Normal file
66
docker/nix.Dockerfile
Normal 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
26
flake.lock
generated
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
13
flake.nix
13
flake.nix
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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
54
nix/ci-env.nix
Normal 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"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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
27
nix/packages.nix
Normal 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
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
)
|
||||
|
||||
@@ -91,7 +91,14 @@ mptIssueFromJson(json::Value const& v)
|
||||
Throw<json::Error>("mptIssueFromJson MPTID is invalid");
|
||||
}
|
||||
|
||||
return MPTIssue{id};
|
||||
MPTIssue const mptIssue{id};
|
||||
auto const& issuer = mptIssue.getIssuer();
|
||||
if (issuer == noAccount() || issuer == xrpAccount())
|
||||
{
|
||||
Throw<json::Error>("mptIssueFromJson issuer must be a valid account");
|
||||
}
|
||||
|
||||
return mptIssue;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
@@ -31,39 +33,53 @@ STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
|
||||
if (isXRP(Currency::fromRaw(currencyOrAccount)))
|
||||
{
|
||||
asset_ = xrpIssue();
|
||||
return;
|
||||
}
|
||||
// Check if MPT
|
||||
else
|
||||
|
||||
// The next 160-bit field selects the format:
|
||||
// noAccount → MPT V1 (pre-fixCleanup3_2_0)
|
||||
// xrpAccount → MPT V2 (fixCleanup3_2_0)
|
||||
// else → regular IOU; the field is the issuer account.
|
||||
//
|
||||
// Both MPT versions carry the 4-byte sequence next, but differ
|
||||
// in byte order:
|
||||
//
|
||||
// V1 uses add32()/get32(), which swap host↔BE. The source
|
||||
// bytes (first 4 of MPTID) are already canonical BE per
|
||||
// makeMptID(), so on LE hosts the swap emits a byte-reversed
|
||||
// sequence to the wire. Reads invert the same swap, so
|
||||
// round-trips on a single arch are consistent.
|
||||
//
|
||||
// V2 uses addRaw()/getRaw(): the canonical BE bytes from
|
||||
// makeMptID() reach the wire untouched.
|
||||
AccountID const account = AccountID::fromRaw(sit.get160());
|
||||
if (account == noAccount() || account == xrpAccount())
|
||||
{
|
||||
// MPT is serialized as:
|
||||
// - 160 bits MPT issuer account
|
||||
// - 160 bits black hole account
|
||||
// - 32 bits sequence
|
||||
AccountID const account = AccountID::fromRaw(sit.get160());
|
||||
// MPT
|
||||
if (noAccount() == account)
|
||||
MPTID mptID{};
|
||||
auto constexpr kSEQ_SIZE = sizeof(std::uint32_t);
|
||||
if (account == noAccount())
|
||||
{
|
||||
MPTID mptID;
|
||||
std::uint32_t sequence = sit.get32();
|
||||
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
|
||||
memcpy(mptID.data(), &sequence, sizeof(sequence));
|
||||
memcpy(
|
||||
mptID.data() + sizeof(sequence),
|
||||
currencyOrAccount.data(),
|
||||
sizeof(currencyOrAccount));
|
||||
MPTIssue const issue{mptID};
|
||||
asset_ = issue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Issue issue;
|
||||
issue.currency = currencyOrAccount;
|
||||
issue.account = account;
|
||||
if (!isConsistent(issue))
|
||||
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
|
||||
asset_ = issue;
|
||||
auto const rawBytes = sit.getRaw(kSEQ_SIZE);
|
||||
memcpy(mptID.data(), rawBytes.data(), rawBytes.size());
|
||||
}
|
||||
static_assert(MPTID::size() == kSEQ_SIZE + sizeof(currencyOrAccount));
|
||||
memcpy(mptID.data() + kSEQ_SIZE, currencyOrAccount.data(), sizeof(currencyOrAccount));
|
||||
MPTIssue const issue{mptID};
|
||||
asset_ = issue;
|
||||
return;
|
||||
}
|
||||
|
||||
Issue issue;
|
||||
issue.currency = currencyOrAccount;
|
||||
issue.account = account;
|
||||
if (!isConsistent(issue))
|
||||
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
|
||||
asset_ = issue;
|
||||
}
|
||||
|
||||
SerializedTypeID
|
||||
@@ -96,11 +112,28 @@ STIssue::add(Serializer& s) const
|
||||
s.addBitString(issue.account);
|
||||
},
|
||||
[&](MPTIssue const& issue) {
|
||||
auto const fixSerializationEnabled = isFeatureEnabled(fixCleanup3_2_0, false);
|
||||
s.addBitString(issue.getIssuer());
|
||||
s.addBitString(noAccount());
|
||||
std::uint32_t sequence = 0;
|
||||
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
|
||||
s.add32(sequence);
|
||||
// The sentinel distinguishes V2 (xrpAccount) from V1 (noAccount)
|
||||
// during deserialization; see the constructor for the full format
|
||||
// description.
|
||||
s.addBitString(fixSerializationEnabled ? xrpAccount() : noAccount());
|
||||
if (fixSerializationEnabled)
|
||||
{
|
||||
// memcpy preserves the byte pattern exactly, so for V2, addRaw()
|
||||
// emits the same canonical bytes that were in the MPTID.
|
||||
s.addRaw(issue.getMptID().data(), sizeof(std::uint32_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy the first 4 bytes of the MPTID (the canonical BE sequence)
|
||||
// into a uint32_t so we can pass either to add32() or addRaw().
|
||||
// For V1, add32() applies a native-to-BE swap on top of what is
|
||||
// already a BE-in-memory value, producing LE wire bytes on LE hosts.
|
||||
std::uint32_t sequence = 0;
|
||||
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
|
||||
s.add32(sequence);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -3,13 +3,24 @@
|
||||
#include <test/jtx/amount.h> // IWYU pragma: keep
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/hash/uhash.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
@@ -137,12 +148,203 @@ public:
|
||||
"000000000000000000000000000000000000000000000002");
|
||||
}
|
||||
|
||||
// Hard-coded wire-format fixture.
|
||||
//
|
||||
// Verifies what STIssue::add() actually puts on the wire for the 4-byte
|
||||
// sequence field of an MPT issue.
|
||||
//
|
||||
// Before fix (V1, no amendment): add32() applies a native-to-BE swap on
|
||||
// top of MPTID bytes that are already canonical BE. On LE hosts the two
|
||||
// swaps cancel and the wire bytes end up in LE order — the opposite of
|
||||
// what a conforming client expects.
|
||||
//
|
||||
// After fix (V2, amendment enabled): addRaw() writes the MPTID bytes
|
||||
// verbatim. The wire bytes match the canonical BE encoding from makeMptID().
|
||||
void
|
||||
testMPTWireFormat()
|
||||
{
|
||||
testcase("MPT serialization - serialized sequence bytes are canonical big-endian");
|
||||
using namespace jtx;
|
||||
Account const alice{"alice"};
|
||||
BEAST_EXPECT(std::endian::native == std::endian::little);
|
||||
|
||||
// Sequence 240 = 0x000000F0.
|
||||
// Canonical BE bytes a client would expect: {0x00, 0x00, 0x00, 0xF0}.
|
||||
// Serialized layout: issuer(20) + marker(20) + sequence(4).
|
||||
MPTID const mptID240 = makeMptID(240, alice.id());
|
||||
|
||||
// Before fix: wire sequence bytes are LE-swapped, not canonical.
|
||||
{
|
||||
STIssue const st(sfAsset, Asset{MPTIssue{mptID240}});
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
Slice const sl = s.slice();
|
||||
BEAST_EXPECT(sl.size() == 44);
|
||||
BEAST_EXPECT(sl[40] == 0xF0); // ← wrong: LSB of 0xF0000000 on LE
|
||||
BEAST_EXPECT(sl[41] == 0x00);
|
||||
BEAST_EXPECT(sl[42] == 0x00);
|
||||
BEAST_EXPECT(sl[43] == 0x00);
|
||||
}
|
||||
|
||||
// After fix: wire sequence bytes are canonical BE.
|
||||
{
|
||||
std::unordered_set<uint256, beast::Uhash<>> const presets{fixCleanup3_2_0};
|
||||
CurrentTransactionRulesGuard const guard(Rules{presets});
|
||||
|
||||
STIssue const st(sfAsset, Asset{MPTIssue{mptID240}});
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
Slice const sl = s.slice();
|
||||
BEAST_EXPECT(sl.size() == 44);
|
||||
BEAST_EXPECT(sl[40] == 0x00); // ← correct: MSB of 0x000000F0
|
||||
BEAST_EXPECT(sl[41] == 0x00);
|
||||
BEAST_EXPECT(sl[42] == 0x00);
|
||||
BEAST_EXPECT(sl[43] == 0xF0);
|
||||
}
|
||||
|
||||
// 0xDEADBEEF: non-palindromic value makes the byte-order contrast
|
||||
// unambiguous regardless of host endianness.
|
||||
MPTID const mptIDBEEF = makeMptID(0xDEADBEEF, alice.id());
|
||||
|
||||
// Before fix: {0xEF, 0xBE, 0xAD, 0xDE} — LE-swapped.
|
||||
{
|
||||
STIssue const st(sfAsset, Asset{MPTIssue{mptIDBEEF}});
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
Slice const sl = s.slice();
|
||||
BEAST_EXPECT(sl[40] == 0xEF);
|
||||
BEAST_EXPECT(sl[41] == 0xBE);
|
||||
BEAST_EXPECT(sl[42] == 0xAD);
|
||||
BEAST_EXPECT(sl[43] == 0xDE);
|
||||
}
|
||||
|
||||
// After fix: {0xDE, 0xAD, 0xBE, 0xEF} — canonical BE.
|
||||
{
|
||||
std::unordered_set<uint256, beast::Uhash<>> const presets{fixCleanup3_2_0};
|
||||
CurrentTransactionRulesGuard const guard(Rules{presets});
|
||||
|
||||
STIssue const st(sfAsset, Asset{MPTIssue{mptIDBEEF}});
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
Slice const sl = s.slice();
|
||||
BEAST_EXPECT(sl[40] == 0xDE);
|
||||
BEAST_EXPECT(sl[41] == 0xAD);
|
||||
BEAST_EXPECT(sl[42] == 0xBE);
|
||||
BEAST_EXPECT(sl[43] == 0xEF);
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-path test.
|
||||
//
|
||||
// Verifies that the STIssue codec (add/deserialize) agrees with the JSON
|
||||
// path (mptIssueFromJson) on the meaning of a raw MPTID.
|
||||
//
|
||||
// The sentinels (noAccount for V1, xrpAccount for V2) are internal codec
|
||||
// details. Clients only ever hold the raw 24-byte MPTID returned by RPC;
|
||||
// they never construct sentinel bytes themselves.
|
||||
//
|
||||
// Before fix (V1): the deserializer calls get32(), which byte-swaps the
|
||||
// canonical BE sequence bytes on LE hosts. The reconstructed MPTID does
|
||||
// not match the original — codec output diverges from JSON output.
|
||||
//
|
||||
// After fix (V2): the deserializer reads the sequence bytes raw. The
|
||||
// reconstructed MPTID matches exactly — codec and JSON paths agree.
|
||||
void
|
||||
testMPTCrossPath()
|
||||
{
|
||||
testcase(
|
||||
"MPT serialization - decoded MPTID matches canonical value: broken in V1, fixed in V2");
|
||||
using namespace jtx;
|
||||
Account const alice{"alice"};
|
||||
|
||||
// Use a non-palindromic sequence so byte-swapping produces a visibly
|
||||
// different MPTID. seq=1 (0x00000001) would give 0x01000000 when
|
||||
// swapped; 0xDEADBEEF is unambiguous on any host.
|
||||
for (auto const seq : {240u, 0xDEADBEEFu, 1u})
|
||||
{
|
||||
MPTID const canonical = makeMptID(seq, alice.id());
|
||||
|
||||
// The JSON path parses the hex string directly into an MPTID —
|
||||
// always canonical. This is the reference value that the codec
|
||||
// (add/deserialize round-trip) must agree with.
|
||||
json::Value jv;
|
||||
jv[jss::mpt_issuance_id] = to_string(canonical);
|
||||
MPTIssue const fromJson = mptIssueFromJson(jv);
|
||||
BEAST_EXPECT(fromJson.getMptID() == canonical);
|
||||
|
||||
// Before fix: V1 codec writes [issuer][noAccount][add32(seq)].
|
||||
// Simulate the deserialization path with canonical (BE) sequence
|
||||
// bytes and V1 marker: get32() byte-swaps on LE, so the
|
||||
// reconstructed MPTID ≠ canonical and ≠ what mptIssueFromJson
|
||||
// produced.
|
||||
{
|
||||
Serializer s;
|
||||
s.addBitString(alice.id());
|
||||
s.addBitString(noAccount());
|
||||
s.addRaw(canonical.data(), sizeof(std::uint32_t));
|
||||
SerialIter sit(s.slice());
|
||||
STIssue const parsed(sit, sfAsset);
|
||||
BEAST_EXPECT(parsed != Asset{MPTIssue{canonical}}); // ← bug
|
||||
BEAST_EXPECT(parsed != Asset{fromJson}); // ← JSON/binary divergence
|
||||
}
|
||||
|
||||
// After fix: V2 codec writes [issuer][xrpAccount][addRaw(seq)].
|
||||
// Same canonical (BE) sequence bytes, V2 marker: getRaw()
|
||||
// preserves bytes, so the reconstructed MPTID == canonical and
|
||||
// == what mptIssueFromJson produced.
|
||||
{
|
||||
Serializer s;
|
||||
s.addBitString(alice.id());
|
||||
s.addBitString(xrpAccount());
|
||||
s.addRaw(canonical.data(), sizeof(std::uint32_t));
|
||||
SerialIter sit(s.slice());
|
||||
STIssue const parsed(sit, sfAsset);
|
||||
BEAST_EXPECT(parsed == Asset{MPTIssue{canonical}}); // ← fixed
|
||||
BEAST_EXPECT(parsed == Asset{fromJson}); // ← JSON/binary agree
|
||||
}
|
||||
}
|
||||
|
||||
// V1 round-trip (no amendment): xrpld's own add() and the
|
||||
// deserializer are symmetrically wrong, so they cancel and the MPTID
|
||||
// survives intact — the bug is invisible in internal round-trips.
|
||||
{
|
||||
for (auto const seq : {240u, 0xDEADBEEFu, 1u})
|
||||
{
|
||||
MPTID const expected = makeMptID(seq, alice.id());
|
||||
STIssue const original(sfAsset, Asset{MPTIssue{expected}});
|
||||
Serializer s;
|
||||
original.add(s);
|
||||
SerialIter sit(s.slice());
|
||||
BEAST_EXPECT(STIssue(sit, sfAsset) == Asset{MPTIssue{expected}});
|
||||
}
|
||||
}
|
||||
|
||||
// V2 full round-trip (amendment enabled): add() and the deserializer
|
||||
// both use canonical bytes — round-trip is correct and canonical.
|
||||
{
|
||||
std::unordered_set<uint256, beast::Uhash<>> const presets{fixCleanup3_2_0};
|
||||
CurrentTransactionRulesGuard const guard(Rules{presets});
|
||||
|
||||
for (auto const seq : {240u, 0xDEADBEEFu, 1u})
|
||||
{
|
||||
MPTID const expected = makeMptID(seq, alice.id());
|
||||
STIssue const original(sfAsset, Asset{MPTIssue{expected}});
|
||||
Serializer s;
|
||||
original.add(s);
|
||||
SerialIter sit(s.slice());
|
||||
BEAST_EXPECT(STIssue(sit, sfAsset) == Asset{MPTIssue{expected}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
// compliments other unit tests to ensure complete coverage
|
||||
testConstructor();
|
||||
testCompare();
|
||||
testMPTWireFormat();
|
||||
testMPTCrossPath();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,15 +2,37 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/JTx.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/utility.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class Submit_test : public beast::unit_test::Suite
|
||||
@@ -86,10 +108,136 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSTIssueV1SignedVaultSubmit()
|
||||
{
|
||||
testcase("V1 STIssue signed Vault tx succeeds submit signature verification");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(100'000), issuer, owner);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.current()->rules().enabled(fixCleanup3_2_0));
|
||||
|
||||
MPTTester mpt{env, issuer, kMPT_INIT_NO_FUND};
|
||||
mpt.create({.flags = tfMPTCanTransfer | tfMPTRequireAuth});
|
||||
mpt.authorize({.account = owner});
|
||||
mpt.authorize({.account = issuer, .holder = owner});
|
||||
|
||||
PrettyAsset const asset = mpt.issuanceID();
|
||||
env(pay(issuer, owner, asset(100)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [jv, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
(void)keylet;
|
||||
jv[jss::Fee] = to_string(env.current()->fees().base);
|
||||
jv[jss::Sequence] = env.seq(owner);
|
||||
jv[jss::SigningPubKey] = strHex(owner.pk().slice());
|
||||
|
||||
STTx tx{parse(jv)};
|
||||
tx.sign(owner.pk(), owner.sk());
|
||||
BEAST_EXPECT(tx.checkSign(env.current()->rules()));
|
||||
|
||||
auto const jrr = env.rpc("submit", strHex(tx.getSerializer().slice()))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::engine_result] == "tesSUCCESS");
|
||||
}
|
||||
|
||||
void
|
||||
testSTIssueV2SignedVaultSubmit()
|
||||
{
|
||||
testcase("V2 STIssue signed Vault tx fails submit signature verification");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(100'000), issuer, owner);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.current()->rules().enabled(fixCleanup3_2_0));
|
||||
|
||||
MPTTester mpt{env, issuer, kMPT_INIT_NO_FUND};
|
||||
mpt.create({.flags = tfMPTCanTransfer | tfMPTRequireAuth});
|
||||
mpt.authorize({.account = owner});
|
||||
mpt.authorize({.account = issuer, .holder = owner});
|
||||
|
||||
PrettyAsset const asset = mpt.issuanceID();
|
||||
env(pay(issuer, owner, asset(100)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [jv, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
(void)keylet;
|
||||
jv[jss::Fee] = to_string(env.current()->fees().base);
|
||||
jv[jss::Sequence] = env.seq(owner);
|
||||
jv[jss::SigningPubKey] = strHex(owner.pk().slice());
|
||||
|
||||
// Model an external client that already writes the V2 STIssue wire
|
||||
// format. The test must not rely on CurrentTransactionRulesGuard to
|
||||
// produce the bytes that are signed.
|
||||
MPTIssue const mptIssue = asset.raw().get<MPTIssue>();
|
||||
auto const serializeV1Asset = [&mptIssue]() {
|
||||
Serializer s;
|
||||
STIssue const st{sfAsset, Asset{mptIssue}};
|
||||
st.addFieldID(s);
|
||||
st.add(s);
|
||||
return s.getData();
|
||||
};
|
||||
auto const serializeV2Asset = [&mptIssue]() {
|
||||
Serializer s;
|
||||
STIssue const st{sfAsset, Asset{mptIssue}};
|
||||
st.addFieldID(s);
|
||||
s.addBitString(mptIssue.getIssuer());
|
||||
s.addBitString(xrpAccount());
|
||||
s.addRaw(mptIssue.getMptID().data(), sizeof(std::uint32_t));
|
||||
return s.getData();
|
||||
};
|
||||
auto const replaceAsset = [this](Blob data, Blob const& from, Blob const& to) {
|
||||
BEAST_EXPECT(from.size() == to.size());
|
||||
auto found = std::ranges::search(data, from);
|
||||
BEAST_EXPECT(!found.empty());
|
||||
if (!found.empty())
|
||||
std::ranges::copy(to, found.begin());
|
||||
return data;
|
||||
};
|
||||
|
||||
Blob const v1Asset = serializeV1Asset();
|
||||
Blob const v2Asset = serializeV2Asset();
|
||||
BEAST_EXPECT(v1Asset != v2Asset);
|
||||
|
||||
STTx tx{parse(jv)};
|
||||
Serializer signingData;
|
||||
signingData.add32(HashPrefix::TxSign);
|
||||
tx.addWithoutSigningFields(signingData);
|
||||
Blob const clientSigningData = replaceAsset(signingData.getData(), v1Asset, v2Asset);
|
||||
auto const sig = sign(owner.pk(), owner.sk(), makeSlice(clientSigningData));
|
||||
Slice const sigSlice{sig.data(), sig.size()};
|
||||
BEAST_EXPECT(verify(owner.pk(), makeSlice(clientSigningData), sigSlice));
|
||||
tx.setFieldVL(sfTxnSignature, sigSlice);
|
||||
|
||||
Serializer txSerializer;
|
||||
tx.add(txSerializer);
|
||||
Blob const clientTx = replaceAsset(txSerializer.getData(), v1Asset, v2Asset);
|
||||
|
||||
SerialIter sit{makeSlice(clientTx)};
|
||||
STTx const submittedTx{sit};
|
||||
BEAST_EXPECT(submittedTx[sfAsset] == asset.raw());
|
||||
BEAST_EXPECT(!submittedTx.checkSign(env.current()->rules()));
|
||||
|
||||
auto const jrr = env.rpc("submit", strHex(clientTx))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::error_exception].asString().find("Invalid signature") != std::string::npos);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testFailHardValidation();
|
||||
testSTIssueV1SignedVaultSubmit();
|
||||
testSTIssueV2SignedVaultSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/handlers/Handlers.h>
|
||||
#include <xrpld/rpc/handlers/account/AccountInfo.h>
|
||||
#include <xrpld/rpc/handlers/ledger/Ledger.h>
|
||||
#include <xrpld/rpc/handlers/server_info/Version.h>
|
||||
|
||||
@@ -49,16 +48,6 @@ handle(JsonContext& context, Object& object)
|
||||
context.apiVersion >= HandlerImpl::minApiVer &&
|
||||
context.apiVersion <= HandlerImpl::maxApiVer,
|
||||
"xrpl::RPC::handle : valid API version");
|
||||
|
||||
if constexpr (requires { HandlerImpl::requestFields; })
|
||||
{
|
||||
if (auto status = validateFieldSpecs(context.params, HandlerImpl::requestFields))
|
||||
{
|
||||
status.inject(object);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
HandlerImpl handler(context);
|
||||
|
||||
auto status = handler.check();
|
||||
@@ -89,6 +78,10 @@ handlerFrom()
|
||||
Handler const kHANDLER_ARRAY[]{
|
||||
// Some handlers not specified here are added to the table via addHandler()
|
||||
// Request-response methods
|
||||
{.name = "account_info",
|
||||
.valueMethod = byRef(&doAccountInfo),
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_currencies",
|
||||
.valueMethod = byRef(&doAccountCurrencies),
|
||||
.role = Role::USER,
|
||||
@@ -417,7 +410,6 @@ private:
|
||||
}
|
||||
|
||||
// This is where the new-style handlers are added.
|
||||
addHandler<AccountInfoHandler>();
|
||||
addHandler<LedgerHandler>();
|
||||
addHandler<VersionHandler>();
|
||||
}
|
||||
|
||||
@@ -6,11 +6,8 @@
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
|
||||
#include <span>
|
||||
|
||||
namespace json {
|
||||
class Object;
|
||||
} // namespace json
|
||||
@@ -39,37 +36,9 @@ struct Handler
|
||||
unsigned maxApiVer = kAPI_MAXIMUM_VALID_VERSION;
|
||||
};
|
||||
|
||||
enum class FieldRequirement { Optional, Required };
|
||||
|
||||
struct FieldSpec
|
||||
{
|
||||
json::StaticString name;
|
||||
FieldRequirement requirement;
|
||||
json::ValueType type;
|
||||
};
|
||||
|
||||
Handler const*
|
||||
getHandler(unsigned int version, bool betaEnabled, std::string const&);
|
||||
|
||||
inline Status
|
||||
validateFieldSpecs(json::Value const& params, std::span<FieldSpec const> fields)
|
||||
{
|
||||
for (auto const& field : fields)
|
||||
{
|
||||
if (!params.isMember(field.name))
|
||||
{
|
||||
if (field.requirement == FieldRequirement::Required)
|
||||
return {RpcInvalidParams, missingFieldMessage(std::string(field.name))};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (params[field.name].type() != field.type)
|
||||
return {RpcInvalidParams, invalidFieldMessage(field.name)};
|
||||
}
|
||||
|
||||
return Status::kOK;
|
||||
}
|
||||
|
||||
/** Return a json::ValueType::Object with a single entry. */
|
||||
template <class Value>
|
||||
json::Value
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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()] = "";
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#include <xrpld/rpc/handlers/account/AccountInfo.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
@@ -94,10 +91,14 @@ doAccountInfo(RPC::JsonContext& context)
|
||||
std::string strIdent;
|
||||
if (params.isMember(jss::account))
|
||||
{
|
||||
if (!params[jss::account].isString())
|
||||
return RPC::invalidFieldError(jss::account);
|
||||
strIdent = params[jss::account].asString();
|
||||
}
|
||||
else if (params.isMember(jss::ident))
|
||||
{
|
||||
if (!params[jss::ident].isString())
|
||||
return RPC::invalidFieldError(jss::ident);
|
||||
strIdent = params[jss::ident].asString();
|
||||
}
|
||||
else
|
||||
@@ -339,24 +340,4 @@ doAccountInfo(RPC::JsonContext& context)
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace RPC {
|
||||
|
||||
AccountInfoHandler::AccountInfoHandler(JsonContext& context) : context_(context)
|
||||
{
|
||||
}
|
||||
|
||||
Status
|
||||
AccountInfoHandler::check()
|
||||
{
|
||||
return Status::kOK;
|
||||
}
|
||||
|
||||
void
|
||||
AccountInfoHandler::writeResult(json::Value& value)
|
||||
{
|
||||
value = doAccountInfo(context_);
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace xrpl::RPC {
|
||||
|
||||
class AccountInfoHandler
|
||||
{
|
||||
public:
|
||||
explicit AccountInfoHandler(JsonContext&);
|
||||
|
||||
static Status
|
||||
check();
|
||||
|
||||
void
|
||||
writeResult(json::Value&);
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static constexpr char name[] = "account_info";
|
||||
|
||||
static constexpr unsigned minApiVer = RPC::kAPI_MINIMUM_SUPPORTED_VERSION;
|
||||
|
||||
static constexpr unsigned maxApiVer = RPC::kAPI_MAXIMUM_VALID_VERSION;
|
||||
|
||||
static constexpr Role role = Role::USER;
|
||||
|
||||
static constexpr Condition condition = Condition::NoCondition;
|
||||
|
||||
static constexpr std::array requestFields = {
|
||||
FieldSpec{
|
||||
.name = jss::account,
|
||||
.requirement = FieldRequirement::Optional,
|
||||
.type = json::ValueType::String},
|
||||
FieldSpec{
|
||||
.name = jss::ident,
|
||||
.requirement = FieldRequirement::Optional,
|
||||
.type = json::ValueType::String},
|
||||
FieldSpec{
|
||||
.name = jss::queue,
|
||||
.requirement = FieldRequirement::Optional,
|
||||
.type = json::ValueType::Boolean},
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
JsonContext& context_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::RPC
|
||||
Reference in New Issue
Block a user