mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-09 08:10:28 +00:00
Compare commits
38 Commits
3.3.0-b1
...
ximinez/31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e6ee18da | ||
|
|
c7adb215ed | ||
|
|
71ee0f400b | ||
|
|
3555812b68 | ||
|
|
203d7d6bf8 | ||
|
|
2a30d519b9 | ||
|
|
7f0dd14235 | ||
|
|
ea64e9f869 | ||
|
|
56c4315834 | ||
|
|
e57be78e50 | ||
|
|
8d5834d06b | ||
|
|
140c534613 | ||
|
|
0e518b6fba | ||
|
|
241ba86467 | ||
|
|
4bb15b91c2 | ||
|
|
03520f04d1 | ||
|
|
538f32728f | ||
|
|
0999af81dc | ||
|
|
e9973cf0c4 | ||
|
|
3f511e8067 | ||
|
|
65691f7ef1 | ||
|
|
3268c63f1a | ||
|
|
7c2a1297db | ||
|
|
a16d15660e | ||
|
|
fd406e2500 | ||
|
|
73dca1111a | ||
|
|
923a822491 | ||
|
|
9ab4f325ce | ||
|
|
05ce347036 | ||
|
|
f00c07da2f | ||
|
|
850dba46fb | ||
|
|
2853c64974 | ||
|
|
dd95b326b3 | ||
|
|
f2a22d4b99 | ||
|
|
0fc1c735a1 | ||
|
|
ea7a7de5de | ||
|
|
62bce4e178 | ||
|
|
3086504658 |
@@ -159,6 +159,7 @@ test.peerfinder > xrpl.protocol
|
||||
test.protocol > test.jtx
|
||||
test.protocol > test.unit_test
|
||||
test.protocol > xrpl.basics
|
||||
test.protocol > xrpld.core
|
||||
test.protocol > xrpl.json
|
||||
test.protocol > xrpl.protocol
|
||||
test.rpc > test.jtx
|
||||
|
||||
15
docs/build/environment.md
vendored
15
docs/build/environment.md
vendored
@@ -33,9 +33,10 @@ with a single command and without installing anything system-wide:
|
||||
nix --experimental-features 'nix-command flakes' develop
|
||||
```
|
||||
|
||||
On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses
|
||||
your **system-wide Apple Clang** as the compiler, so you still need to manage
|
||||
its version (see below).
|
||||
On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides
|
||||
Clang. If you instead opt to use your system-wide Apple Clang (via
|
||||
`nix develop .#apple-clang`), you need to manage its version yourself (see
|
||||
below).
|
||||
|
||||
See [Using the Nix development shell](./nix.md) for installation and usage
|
||||
details, including how to select a different compiler.
|
||||
@@ -48,10 +49,10 @@ details, including how to select a different compiler.
|
||||
|
||||
### macOS: managing the Apple Clang version
|
||||
|
||||
Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler
|
||||
version is whatever your installed Xcode (or Command Line Tools) provides. The
|
||||
following command should return a version greater than or equal to the
|
||||
[minimum required](#tested-compiler-versions):
|
||||
If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`),
|
||||
the compiler version is whatever your installed Xcode (or Command Line Tools)
|
||||
provides. The following command should return a version greater than or equal to
|
||||
the [minimum required](#tested-compiler-versions):
|
||||
|
||||
```bash
|
||||
clang --version
|
||||
|
||||
40
docs/build/nix.md
vendored
40
docs/build/nix.md
vendored
@@ -9,7 +9,7 @@ This guide explains how to use Nix to set up a reproducible development environm
|
||||
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
|
||||
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment
|
||||
- **No system pollution**: Dependencies are isolated and don't affect your system packages
|
||||
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
|
||||
- **Consistent compilers**: The GCC and Clang shells use the same versions as CI
|
||||
- **Quick setup**: Get started with a single command
|
||||
- **Works on Linux and macOS**: Consistent experience across platforms
|
||||
|
||||
@@ -31,8 +31,8 @@ This will:
|
||||
|
||||
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
|
||||
- Configure the appropriate compiler for your platform:
|
||||
- **Linux**: GCC 15.2 (provided by Nix)
|
||||
- **macOS**: Apple Clang (your system compiler)
|
||||
- **Linux**: GCC (provided by Nix)
|
||||
- **macOS**: Clang (provided by Nix)
|
||||
|
||||
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
|
||||
|
||||
@@ -40,12 +40,12 @@ The first time you run this command, it will take a few minutes to download and
|
||||
|
||||
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to
|
||||
develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats.
|
||||
- **macOS**: `nix develop` gives you a full environment too. The compiler is
|
||||
your system-wide Apple Clang, while every other tool — including Conan — is
|
||||
provided by Nix. Conan has no binary in the Nix cache for macOS, so it is
|
||||
built from source the first time you enter the shell, which makes the initial
|
||||
setup slower (this is handled automatically; see
|
||||
[`nix/devshell.nix`](../../nix/devshell.nix)).
|
||||
- **macOS**: `nix develop` gives you a full environment too, with Clang (and
|
||||
every other tool, including Conan) provided by Nix. To use your system-wide
|
||||
Apple Clang instead, enter `nix develop .#apple-clang`. Conan has no binary in
|
||||
the Nix cache for macOS, so it is built from source the first time you enter
|
||||
the shell, which makes the initial setup slower (this is handled
|
||||
automatically; see [`nix/devshell.nix`](../../nix/devshell.nix)).
|
||||
|
||||
> [!TIP]
|
||||
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
|
||||
@@ -62,7 +62,9 @@ The first time you run this command, it will take a few minutes to download and
|
||||
|
||||
### Choosing a different compiler
|
||||
|
||||
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
|
||||
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#clang`.
|
||||
The `.#gcc` and `.#clang` shells provide the same GCC and Clang versions used in CI
|
||||
(pinned in [`nix/packages.nix`](../../nix/packages.nix)).
|
||||
Use `nix flake show` to see all the available development shells.
|
||||
|
||||
Use `nix develop .#no-compiler` to use the compiler from your system.
|
||||
@@ -70,11 +72,11 @@ Use `nix develop .#no-compiler` to use the compiler from your system.
|
||||
### Example Usage
|
||||
|
||||
```bash
|
||||
# Use GCC 14
|
||||
nix develop .#gcc14
|
||||
# Use GCC (same version as CI)
|
||||
nix develop .#gcc
|
||||
|
||||
# Use Clang 19
|
||||
nix develop .#clang19
|
||||
# Use Clang (same version as CI)
|
||||
nix develop .#clang
|
||||
|
||||
# Use default for your platform
|
||||
nix develop
|
||||
@@ -112,7 +114,15 @@ Once inside the Nix development shell, follow the standard [build instructions](
|
||||
|
||||
[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory.
|
||||
|
||||
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`.
|
||||
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`.
|
||||
|
||||
The repository already ships an `.envrc` at its root that activates the Nix flake development shell, so you don't need to create one. To use it:
|
||||
|
||||
1. [Install direnv](https://direnv.net/docs/installation.html) and [hook it into your shell](https://direnv.net/docs/hook.html) (bash, zsh, fish, …). Installing [nix-direnv](https://github.com/nix-community/nix-direnv) as well is recommended: it caches the shell so that activation is near-instant after the first run.
|
||||
2. Run `direnv allow` once in the repository root. direnv will then load (and reload) the Nix development shell automatically whenever you enter the directory.
|
||||
|
||||
> [!NOTE]
|
||||
> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected.
|
||||
|
||||
## Conan and Prebuilt Packages
|
||||
|
||||
|
||||
@@ -305,7 +305,9 @@ public:
|
||||
XRPL_ASSERT(
|
||||
c.size() * sizeof(typename Container::value_type) == size(),
|
||||
"xrpl::BaseUInt::fromRaw(Container auto) : input size match");
|
||||
std::memcpy(result.data_.data(), c.data(), size());
|
||||
std::size_t const canCopy =
|
||||
std::min(size(), c.size() * sizeof(typename Container::value_type));
|
||||
std::memcpy(result.data_.data(), c.data(), canCopy);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -319,7 +321,11 @@ public:
|
||||
XRPL_ASSERT(
|
||||
c.size() * sizeof(typename Container::value_type) == size(),
|
||||
"xrpl::BaseUInt::operator=(Container auto) : input size match");
|
||||
std::memcpy(data_.data(), c.data(), size());
|
||||
std::size_t const canCopy =
|
||||
std::min(size(), c.size() * sizeof(typename Container::value_type));
|
||||
if (canCopy < size())
|
||||
*this = beast::kZero;
|
||||
std::memcpy(data_.data(), c.data(), canCopy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
|
||||
inherit (pkgs) lib;
|
||||
inherit (import ./packages.nix { inherit pkgs; })
|
||||
commonPackages
|
||||
gccPackage
|
||||
llvmPackages
|
||||
llvmVersion
|
||||
;
|
||||
|
||||
# Underlying compiler toolchains to wrap. Bump these in one place to
|
||||
# roll the whole environment forward.
|
||||
customGccPackage = pkgs.gcc15;
|
||||
customLlvmPackages = pkgs.llvmPackages_22;
|
||||
customClangMajor = lib.versions.major (lib.getVersion customLlvmPackages.clang-unwrapped);
|
||||
# Underlying compiler toolchains to wrap (versions pinned in packages.nix).
|
||||
customGccPackage = gccPackage;
|
||||
customLlvmPackages = llvmPackages;
|
||||
|
||||
# binutils wrapped to emit binaries that reference the custom glibc
|
||||
# (dynamic linker path, library search path, RPATH).
|
||||
@@ -90,7 +92,7 @@ let
|
||||
extraBuildCommands = ''
|
||||
rsrc="$out/resource-root"
|
||||
mkdir "$rsrc"
|
||||
ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${customClangMajor}/include" "$rsrc/include"
|
||||
ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${toString llvmVersion}/include" "$rsrc/include"
|
||||
ln -s "${customCompilerRt.out}/lib" "$rsrc/lib"
|
||||
ln -s "${customCompilerRt.out}/share" "$rsrc/share" || true
|
||||
echo "-resource-dir=$rsrc" >> $out/nix-support/cc-cflags
|
||||
|
||||
150
nix/devshell.nix
150
nix/devshell.nix
@@ -1,127 +1,57 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
|
||||
inherit (import ./packages.nix { inherit pkgs; })
|
||||
commonPackages
|
||||
gccVersion
|
||||
llvmPackages
|
||||
;
|
||||
|
||||
# Supported compiler versions
|
||||
gccVersion = pkgs.lib.range 13 15;
|
||||
clangVersions = pkgs.lib.range 18 21;
|
||||
# Plain nixpkgs stdenvs — no custom glibc, unlike ci-env.nix.
|
||||
gccStdenv = pkgs."gcc${toString gccVersion}Stdenv";
|
||||
clangStdenv = llvmPackages.stdenv;
|
||||
|
||||
defaultCompiler = if pkgs.stdenv.isDarwin then "apple-clang" else "gcc";
|
||||
defaultGccVersion = pkgs.lib.last gccVersion;
|
||||
defaultClangVersion = pkgs.lib.last clangVersions;
|
||||
|
||||
strToCompilerEnv =
|
||||
compiler: version:
|
||||
(
|
||||
if compiler == "gcc" then
|
||||
let
|
||||
gccPkg = pkgs."gcc${toString version}Stdenv" or null;
|
||||
in
|
||||
if gccPkg != null && builtins.elem version gccVersion then
|
||||
gccPkg
|
||||
else
|
||||
throw "Invalid GCC version: ${toString version}. Must be one of: ${toString gccVersion}"
|
||||
else if compiler == "clang" then
|
||||
let
|
||||
clangPkg = pkgs."llvmPackages_${toString version}".stdenv or null;
|
||||
in
|
||||
if clangPkg != null && builtins.elem version clangVersions then
|
||||
clangPkg
|
||||
else
|
||||
throw "Invalid Clang version: ${toString version}. Must be one of: ${toString clangVersions}"
|
||||
else if compiler == "apple-clang" || compiler == "none" then
|
||||
pkgs.stdenvNoCC
|
||||
else
|
||||
throw "Invalid compiler: ${compiler}. Must be one of: gcc, clang, apple-clang, none"
|
||||
);
|
||||
|
||||
# Helper function to create a shell with a specific compiler
|
||||
# compilerName is the command used to print the version, or null for none.
|
||||
makeShell =
|
||||
{
|
||||
compiler ? defaultCompiler,
|
||||
version ? (
|
||||
if compiler == "gcc" then
|
||||
defaultGccVersion
|
||||
else if compiler == "clang" then
|
||||
defaultClangVersion
|
||||
else
|
||||
null
|
||||
),
|
||||
stdenv,
|
||||
compilerName,
|
||||
}:
|
||||
let
|
||||
compilerStdEnv = strToCompilerEnv compiler version;
|
||||
|
||||
compilerName =
|
||||
if compiler == "apple-clang" then
|
||||
"clang"
|
||||
else if compiler == "none" then
|
||||
null
|
||||
else
|
||||
compiler;
|
||||
|
||||
gccOnMacWarning =
|
||||
if pkgs.stdenv.isDarwin && compiler == "gcc" then
|
||||
''
|
||||
echo "WARNING: Using GCC on macOS with Conan may not work."
|
||||
echo " Consider using 'nix develop .#clang' or the default shell instead."
|
||||
echo ""
|
||||
''
|
||||
else
|
||||
"";
|
||||
|
||||
compilerVersion =
|
||||
if compilerName != null then
|
||||
if compilerName == null then
|
||||
''echo "No compiler specified - using system compiler"''
|
||||
else
|
||||
''
|
||||
echo "Compiler: "
|
||||
${compilerName} --version
|
||||
''
|
||||
else
|
||||
''
|
||||
echo "No compiler specified - using system compiler"
|
||||
'';
|
||||
|
||||
shellAttrs = {
|
||||
packages = commonPackages;
|
||||
|
||||
shellHook = ''
|
||||
echo "Welcome to xrpld development shell";
|
||||
${gccOnMacWarning}${compilerVersion}
|
||||
'';
|
||||
};
|
||||
in
|
||||
pkgs.mkShell.override { stdenv = compilerStdEnv; } shellAttrs;
|
||||
|
||||
# Generate shells for each compiler version
|
||||
gccShells = builtins.listToAttrs (
|
||||
map (version: {
|
||||
name = "gcc${toString version}";
|
||||
value = makeShell {
|
||||
compiler = "gcc";
|
||||
version = version;
|
||||
};
|
||||
}) gccVersion
|
||||
);
|
||||
|
||||
clangShells = builtins.listToAttrs (
|
||||
map (version: {
|
||||
name = "clang${toString version}";
|
||||
value = makeShell {
|
||||
compiler = "clang";
|
||||
version = version;
|
||||
};
|
||||
}) clangVersions
|
||||
);
|
||||
|
||||
(pkgs.mkShell.override { inherit stdenv; }) {
|
||||
packages = commonPackages;
|
||||
shellHook = ''
|
||||
echo "Welcome to xrpld development shell";
|
||||
${compilerVersion}
|
||||
'';
|
||||
};
|
||||
in
|
||||
gccShells
|
||||
// clangShells
|
||||
// {
|
||||
# Default shells
|
||||
default = makeShell { };
|
||||
gcc = makeShell { compiler = "gcc"; };
|
||||
clang = makeShell { compiler = "clang"; };
|
||||
rec {
|
||||
# macOS: Nix Clang. Linux: Nix GCC.
|
||||
default = if pkgs.stdenv.isDarwin then clang else gcc;
|
||||
|
||||
# No compiler
|
||||
no-compiler = makeShell { compiler = "none"; };
|
||||
apple-clang = makeShell { compiler = "apple-clang"; };
|
||||
gcc = makeShell {
|
||||
stdenv = gccStdenv;
|
||||
compilerName = "gcc";
|
||||
};
|
||||
|
||||
clang = makeShell {
|
||||
stdenv = clangStdenv;
|
||||
compilerName = "clang";
|
||||
};
|
||||
|
||||
# Nix provides no compiler; use the one from your system (e.g. Apple Clang).
|
||||
no-compiler = makeShell {
|
||||
stdenv = pkgs.stdenvNoCC;
|
||||
compilerName = null;
|
||||
};
|
||||
apple-clang = no-compiler;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
{ pkgs }:
|
||||
let
|
||||
# Compiler versions used across the dev shell and the CI environment.
|
||||
gccVersion = 15;
|
||||
llvmVersion = 22;
|
||||
|
||||
gccPackage = pkgs."gcc${toString gccVersion}";
|
||||
llvmPackages = pkgs."llvmPackages_${toString llvmVersion}";
|
||||
|
||||
# Bound explicitly so it tracks llvmPackages above, not the `with pkgs` default.
|
||||
clangTools = llvmPackages.clang-tools;
|
||||
|
||||
# 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 "$@"
|
||||
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
|
||||
'';
|
||||
in
|
||||
{
|
||||
inherit
|
||||
gccVersion
|
||||
llvmVersion
|
||||
gccPackage
|
||||
llvmPackages
|
||||
;
|
||||
|
||||
commonPackages = with pkgs; [
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
clangTools
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
@@ -23,7 +41,6 @@ in
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
llvmPackages_22.clang-tools
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
@@ -863,6 +865,101 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
testIssueFromJson()
|
||||
{
|
||||
testcase("issueFromJson");
|
||||
|
||||
// Valid XRP — no issuer field
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "XRP";
|
||||
auto const issue = issueFromJson(jv);
|
||||
BEAST_EXPECT(isXRP(issue));
|
||||
}
|
||||
|
||||
// Valid IOU — legitimate issuer
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
auto const issue = issueFromJson(jv);
|
||||
BEAST_EXPECT(!isXRP(issue));
|
||||
BEAST_EXPECT(issue.account != noAccount());
|
||||
}
|
||||
|
||||
// noAccount() is the MPT sentinel in binary serialization - must be
|
||||
// rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(noAccount());
|
||||
issueFromJson(jv);
|
||||
fail("noAccount() accepted as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// xrpAccount() is the XRP sentinel (all zeros) - must be rejected
|
||||
// as IOU issuer
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(xrpAccount());
|
||||
issueFromJson(jv);
|
||||
fail("xrpAccount() accepted as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// Invalid base58 — must be rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = "not_a_valid_address";
|
||||
issueFromJson(jv);
|
||||
fail("invalid base58 accepted as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// Non-XRP currency with no issuer field — must be rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
issueFromJson(jv);
|
||||
fail("missing issuer accepted");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// XRP with an issuer field — must be rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "XRP";
|
||||
jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
issueFromJson(jv);
|
||||
fail("XRP with issuer accepted");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -897,6 +994,9 @@ public:
|
||||
// ---
|
||||
testIssueDomainSets();
|
||||
testIssueDomainMaps();
|
||||
|
||||
// ---
|
||||
testIssueFromJson();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h> // IWYU pragma: keep
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.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/Issue.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 <memory>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
@@ -137,12 +146,143 @@ public:
|
||||
"000000000000000000000000000000000000000000000002");
|
||||
}
|
||||
|
||||
void
|
||||
testNoAccountIssuerRpc()
|
||||
{
|
||||
testcase("noAccount issuer rejected via RPC sign");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->loadFromString("[signing_support]\ntrue");
|
||||
return cfg;
|
||||
})};
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
json::Value txJson;
|
||||
txJson[jss::TransactionType] = "AMMDelete";
|
||||
txJson[jss::Account] = alice.human();
|
||||
txJson[jss::Asset][jss::currency] = "USD";
|
||||
txJson[jss::Asset][jss::issuer] = to_string(noAccount());
|
||||
txJson[jss::Asset2][jss::currency] = "XRP";
|
||||
|
||||
json::Value req;
|
||||
req[jss::tx_json] = txJson;
|
||||
req[jss::secret] = alice.name();
|
||||
|
||||
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Field 'tx_json.Asset' has invalid data.");
|
||||
}
|
||||
|
||||
void
|
||||
testNoAccountIssuer()
|
||||
{
|
||||
testcase("noAccount issuer rejection");
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(noAccount());
|
||||
|
||||
try
|
||||
{
|
||||
issueFromJson(sfAsset, jv);
|
||||
fail("issueFromJson accepted noAccount() as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Serializer s;
|
||||
s.addBitString(toCurrency("USD"));
|
||||
s.addBitString(noAccount());
|
||||
SerialIter iter(s.slice());
|
||||
|
||||
try
|
||||
{
|
||||
STIssue const stissue(iter, sfAsset);
|
||||
fail(
|
||||
"STIssue deserialization of [USD][noAccount()] should "
|
||||
"throw");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testXrpAccountIssuerRpc()
|
||||
{
|
||||
testcase("xrpAccount issuer rejected via RPC sign");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->loadFromString("[signing_support]\ntrue");
|
||||
return cfg;
|
||||
})};
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
json::Value txJson;
|
||||
txJson[jss::TransactionType] = "AMMDelete";
|
||||
txJson[jss::Account] = alice.human();
|
||||
txJson[jss::Asset][jss::currency] = "USD";
|
||||
txJson[jss::Asset][jss::issuer] = to_string(xrpAccount());
|
||||
txJson[jss::Asset2][jss::currency] = "XRP";
|
||||
|
||||
json::Value req;
|
||||
req[jss::tx_json] = txJson;
|
||||
req[jss::secret] = alice.name();
|
||||
|
||||
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Field 'tx_json.Asset' has invalid data.");
|
||||
}
|
||||
|
||||
void
|
||||
testXrpAccountIssuer()
|
||||
{
|
||||
testcase("xrpAccount issuer rejection");
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(xrpAccount());
|
||||
|
||||
try
|
||||
{
|
||||
issueFromJson(sfAsset, jv);
|
||||
fail("issueFromJson accepted xrpAccount() as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
// compliments other unit tests to ensure complete coverage
|
||||
testConstructor();
|
||||
testCompare();
|
||||
testNoAccountIssuerRpc();
|
||||
testNoAccountIssuer();
|
||||
testXrpAccountIssuerRpc();
|
||||
testXrpAccountIssuer();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/nft.h>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -1350,6 +1352,86 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAccountObjectDoesntShowCancelledOffers()
|
||||
{
|
||||
testcase("AccountObjectDoesntShowCancelledOffers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
auto const eur = bob["EUR"];
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const rpcAccountObjects = [&](std::optional<uint32_t> limit = std::nullopt) {
|
||||
json::Value params;
|
||||
params[jss::account] = alice.human();
|
||||
if (limit.has_value())
|
||||
{
|
||||
params[jss::limit] = *limit;
|
||||
}
|
||||
return env.rpc("json", "account_objects", to_string(params));
|
||||
};
|
||||
|
||||
auto const numEntries = 33;
|
||||
std::vector<uint32_t> seqs;
|
||||
seqs.reserve(numEntries);
|
||||
for ([[maybe_unused]] auto _ : std::ranges::iota_view{0, numEntries})
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::secret] = toBase58(generateSeed("alice"));
|
||||
params[jss::tx_json] = offer(alice, eur(1), XRP(2));
|
||||
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
|
||||
seqs.push_back(env.seq(alice));
|
||||
}
|
||||
|
||||
auto res = rpcAccountObjects();
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == numEntries);
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
|
||||
|
||||
for (auto const s : std::views::all(seqs) | std::views::take(numEntries - 1))
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::secret] = toBase58(generateSeed("alice"));
|
||||
params[jss::tx_json] = offerCancel(alice, s - 1);
|
||||
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
|
||||
}
|
||||
|
||||
res = rpcAccountObjects();
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
|
||||
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::secret] = toBase58(generateSeed("alice"));
|
||||
json::Value txJson;
|
||||
txJson[jss::TransactionType] = jss::NFTokenMint;
|
||||
txJson[jss::Account] = to_string(alice.id());
|
||||
txJson["NFTokenTaxon"] = 1;
|
||||
params[jss::tx_json] = txJson;
|
||||
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
|
||||
}
|
||||
env.close();
|
||||
|
||||
res = rpcAccountObjects();
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 2);
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
|
||||
|
||||
res = rpcAccountObjects(1);
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
|
||||
BEAST_EXPECT(res[jss::result][jss::limit].asUInt() == 1);
|
||||
BEAST_EXPECT(res[jss::result].isMember(jss::marker));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -1360,6 +1442,7 @@ public:
|
||||
testNFTsMarker();
|
||||
testAccountNFTs();
|
||||
testAccountObjectMarker();
|
||||
testAccountObjectDoesntShowCancelledOffers();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/jtx/multisign.h>
|
||||
#include <test/jtx/noop.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/seq.h>
|
||||
@@ -880,6 +881,125 @@ class Transaction_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSignForNetworkIDValidation()
|
||||
{
|
||||
testcase("SignFor NetworkID validation");
|
||||
using namespace test::jtx;
|
||||
|
||||
Account const owner{"owner"};
|
||||
Account const signer{"signer"};
|
||||
|
||||
auto makeConfig = [](std::uint32_t networkID) {
|
||||
return envconfig([networkID](std::unique_ptr<Config> cfg) {
|
||||
cfg->networkId = networkID;
|
||||
return cfg;
|
||||
});
|
||||
};
|
||||
|
||||
auto setupEnv = [&](Env& env) {
|
||||
env.fund(XRP(10'000), owner, signer);
|
||||
env.close();
|
||||
env(signers(owner, 1, {{signer, 1}}));
|
||||
env.close();
|
||||
};
|
||||
|
||||
auto makeTx = [&](Env& env) {
|
||||
json::Value tx;
|
||||
tx[jss::TransactionType] = jss::AccountSet;
|
||||
tx[jss::Account] = owner.human();
|
||||
tx[jss::Sequence] = env.seq(owner);
|
||||
tx[jss::Fee] = "100";
|
||||
tx[jss::SigningPubKey] = "";
|
||||
return tx;
|
||||
};
|
||||
|
||||
auto signFor = [&](Env& env, json::Value const& tx) {
|
||||
json::Value signReq;
|
||||
signReq[jss::tx_json] = tx;
|
||||
signReq[jss::account] = signer.human();
|
||||
signReq[jss::secret] = signer.name();
|
||||
return env.rpc("json", "sign_for", to_string(signReq))[jss::result];
|
||||
};
|
||||
|
||||
// Test case: NetworkID < 1024 - field is not required
|
||||
{
|
||||
Env env{*this, makeConfig(500)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
BEAST_EXPECT(!result[jss::tx_json].isMember(jss::NetworkID));
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - missing NetworkID field
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Missing field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is not a number
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = "not_a_number";
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is not integral
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = 2040.1;
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is different from
|
||||
// actual NetworkID
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = 9999;
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is correct
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = 2040;
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
BEAST_EXPECT(result[jss::tx_json][jss::NetworkID].asUInt() == 2040);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -889,6 +1009,8 @@ public:
|
||||
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testWithFeats(all);
|
||||
|
||||
testSignForNetworkIDValidation();
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -122,6 +122,71 @@ struct BaseUintTest : public ::testing::Test
|
||||
}
|
||||
};
|
||||
|
||||
using BaseUintDeathTest = BaseUintTest;
|
||||
|
||||
TEST_F(BaseUintDeathTest, fromRaw_size_mismatch)
|
||||
{
|
||||
EXPECT_DEBUG_DEATH(
|
||||
({
|
||||
// Container smaller than the base_uint (8 bytes vs 12 bytes for
|
||||
// test96). Only the first 8 bytes are copied; the remaining 4 bytes
|
||||
// stay zero.
|
||||
Blob const tooSmall{1, 2, 3, 4, 5, 6, 7, 8};
|
||||
BaseUInt96 const result = BaseUInt96::fromRaw(tooSmall);
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "010203040506070800000000") << resultText;
|
||||
}),
|
||||
"input size match");
|
||||
|
||||
EXPECT_DEBUG_DEATH(
|
||||
({
|
||||
// Container larger than the base_uint (16 bytes vs 12 bytes for
|
||||
// test96). Only the first 12 bytes are copied; the extra bytes are
|
||||
// ignored.
|
||||
Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
BaseUInt96 const result = BaseUInt96::fromRaw(tooBig);
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "0102030405060708090A0B0C") << resultText;
|
||||
}),
|
||||
"input size match");
|
||||
|
||||
EXPECT_DEBUG_DEATH(
|
||||
({
|
||||
// Container smaller than the base_uint (8 bytes vs 12 bytes for
|
||||
// test96). Only the first 8 bytes are copied; the remaining 4 bytes
|
||||
// stay zero.
|
||||
Blob const tooSmall{1, 2, 3, 4, 5, 6, 7, 8};
|
||||
BaseUInt96 result{};
|
||||
--result;
|
||||
{
|
||||
auto const originalText = to_string(result);
|
||||
EXPECT_EQ(originalText, "FFFFFFFFFFFFFFFFFFFFFFFF") << originalText;
|
||||
}
|
||||
result = tooSmall;
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "010203040506070800000000") << resultText;
|
||||
}),
|
||||
"input size match");
|
||||
|
||||
EXPECT_DEBUG_DEATH(
|
||||
({
|
||||
// Container larger than the base_uint (16 bytes vs 12 bytes for
|
||||
// test96). Only the first 12 bytes are copied; the extra bytes are
|
||||
// ignored.
|
||||
Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
BaseUInt96 result{};
|
||||
--result;
|
||||
{
|
||||
auto const originalText = to_string(result);
|
||||
EXPECT_EQ(originalText, "FFFFFFFFFFFFFFFFFFFFFFFF") << originalText;
|
||||
}
|
||||
result = tooBig;
|
||||
auto const resultText = to_string(result);
|
||||
EXPECT_EQ(resultText, "0102030405060708090A0B0C") << resultText;
|
||||
}),
|
||||
"input size match");
|
||||
}
|
||||
|
||||
TEST_F(BaseUintTest, base_uint)
|
||||
{
|
||||
static_assert(!std::is_constructible_v<BaseUInt96, std::complex<double>>);
|
||||
@@ -198,6 +263,19 @@ TEST_F(BaseUintTest, base_uint)
|
||||
EXPECT_EQ(d, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// There are several ways to create a zero. beast::kZero is tested above. Test some
|
||||
// others.
|
||||
BaseUInt96 const z1;
|
||||
EXPECT_EQ(z1, z) << to_string(z1);
|
||||
|
||||
BaseUInt96 const z2{};
|
||||
EXPECT_EQ(z2, z) << to_string(z2);
|
||||
|
||||
BaseUInt96 const z3{0u};
|
||||
EXPECT_EQ(z3, z) << to_string(z3);
|
||||
}
|
||||
|
||||
BaseUInt96 n{z};
|
||||
n++;
|
||||
EXPECT_EQ(n, BaseUInt96(1));
|
||||
|
||||
Reference in New Issue
Block a user