Compare commits

..
Author SHA1 Message Date
Bart b6531dd532 fix: Select the reply envelope from a ripplerpc the server supports
The `ripplerpc` version is read in three places: twice inside the dispatch
loop to pick the error shape, and once after it to derive the HTTP status
by re-reading the version off the finished reply. Replace all three with
one `shapeReply` keyed by an `RpcVersion` enum naming the three envelopes.
The version is read once per request and passed in, and the status is
returned rather than read back, since only the function that wrote the
shape knows where the code went.

The version is now matched exactly against the three valid spellings, where
the `ripplerpc` string was compared with `>=` against "2.0" and "3.0", so
"abc" read as version 3 and "10.0" as version 1. Junk from an
unauthenticated client therefore chose an envelope, and at version 3 chose
real HTTP error codes. Anything but the three exact values is rejected with
a 400, the malformed-RPC fee and -32602, which is a break for API versions
1 and 2 on the JSON-RPC transport, listed under a breaking-changes heading
of its own in the changelog. The JSON reader now refuses a string holding
a NUL, escaped or raw: `json::Value` keeps a string only through its first
NUL, so "3.0\u0000junk" would otherwise match as "3.0". That reaches every
string field on every transport, so the changelog lists it as well.

Hoisting the log statement above the shape branch fixes a second defect. It
read `error_message` after the version 2 and 3 branch renamed that member
to `message`, and reading a missing member non-const puts it back as an
explicit null, so an error reply carried `"error_message": null` exactly
when the `Server` log partition wrote at debug.
2026-10-25 12:06:14 -04:00
Bart ed9f859059 refactor: Answer every pre-dispatch rejection through one helper
Nine conditions reject a request before it reaches a handler, and seven
of them write their reply twice over: once as a plain-text body with an
HTTP status for a lone request, and once as an error object appended to
the reply array for a batch entry. The same fix has to be made seven
times, and another condition means another copy. Collapse them into one
`reject` helper that answers either shape and reports whether the loop has
more to do, so a caller reads `if (!reject(...)) return; continue;`.

Each shape is preserved exactly, including the asymmetry. Two of the nine
return the entry under `request`: the one naming an `api_version` the
server cannot serve, which asks for that shape through `wrapRequest`, and
an entry that is not an object, which stays inline, having no members to
carry an error. Six carry the error beside the entry's own members, and
`params unparsable` reaches a lone request only, that form's entries being
flat.

No reply changes. The codes these paths report are declared in
protocol/JsonRpc.h beside the protocol version, so the four file-local
copies go. The header declares every JSON-RPC error code this server
reports as one set, so three of them, `kJsonRpcInvalidRequest`,
`kJsonRpcInvalidParams` and `kJsonRpcServerError`, have no user until a
later branch reports them. The consumer now comes from
`requestInboundEndpoint`, which the WebSocket upgrade path already uses,
leaving the overload check as a condition beside the others rather than
nested inside endpoint construction. Two tests pin the shape a rejected
batch entry keeps: a null `method` reports `-32601` with `Null method`,
and an entry that is not an object is echoed under `request`. The suite
gains `overloadEndpoint`, which charges an address past the drop
threshold, for the privileged-request case.
2026-10-25 12:06:13 -04:00
Bart efad8c63b9 fix: Write a status line for every HTTP status a reply can report
`httpReply` names each status in a switch with no `default:` arm, and the
error table names two statuses that switch does not spell out: 402 for
`highFee` and 502 for `dbDeserialization`. A reply reporting either writes
no status line at all, so its first line is a header and the whole thing
is not an HTTP response. A client parsing it reads a protocol error rather
than the error the server meant to report.

Add the arm, taking the reason phrase from beast, which knows the whole
status registry, and drop the `bugprone-switch-missing-default-case`
suppression the omission needed. Eight of the arms above it then spell out
exactly what that arm produces, so they go. Three stay: 401 and 503 report
a phrase of this server's own, and 200 is what every successful reply
carries, so its line stays a compile-time literal rather than a
`std::format` call on the server's most common path.

Both statuses are reachable today through the `ripplerpc: "3.0"` envelope,
which derives the status from the error code. A gtest pins the status line
for every status this server sends, walks the error table, and reads the
placeholder line for a number the registry does not know, so a row added
with a new status cannot reintroduce the defect. A `sign` request whose
fee ceiling is zero reports `highFee` through that envelope, so the server
suite reads the 402 line end to end.
2026-10-25 12:06:13 -04:00
Bart f4473147b7 fix: Give handler-specific RPC errors a code and message
Thirteen sites across five handlers assign `jss::error` a bare token,
skipping the `error_code`/`error_message` pair `rpc::injectError` sets.
Both consequences are visible on the wire: with no `error_code` the HTTP
status defaults to 200, so a load balancer sees success for a failed
request, and the version 2 envelope copies the pair unconditionally, so a
missing source produces an explicit `"code":null`/`"message":null`. Give
those tokens rows of their own, codes 100 to 109, and route every site
through `injectError`, preserving the `error_exception` detail `submit`
and `simulate` attach. `transaction_entry` keeps its four errors in the
handler's output as an rpc-spec `Status`, and `writeResult` reports each
through the status bridge, so the code and message land beside the ledger
fields the reply carries.

The `ledger_entry` helpers still report `invalidParams` (31) rather than
each token's own code. A version 1 or 2 client has read 31 for those tokens
for years, so changing it would break a client matching on the old value; a
comment on the helpers says so. Every test that expected one of the thirteen
sites to report no code, among them each `submit` test that reads
`invalidTransaction`, now expects the new code and message. The server test
reaches a coded error on four of the five handlers. `ledger_accept` reports
`notStandAlone` only on a networked server, which no test harness provides,
so that arm is excluded from coverage with a comment saying why. The
`submit` arm that checked `Transaction::getStatus` after construction is
removed: the constructor sets the status to NEW unless
`STTx::getTransactionID` throws, and that function returns a cached member,
so the arm could not be reached.

The `submit` and `simulate` arms reporting an internal error take no
coverage exclusion. `internalSubmit` is reached through a test-owned
`NetworkOPs` double whose `processTransaction` throws, handed to the handler
through the context it already takes. The two remaining catch blocks,
`internalJson` in `submit` and `internalSimulate`, are live, reached once
`Transaction::getJson` or the simulation throws, and no injection seam
exists for them today, so that gap belongs in the test list rather than
behind a marker that hides it. Two more exclusions in `Simulate.cpp` go, on
arms that are live as well: the `Account` type check, which a numeric
`Account` reaches and a new `simulate` case sends, and the fallback
`engine_result` arm, which gets a comment saying why it stays.
2026-10-25 12:06:12 -04:00
Bart fabd2070bd fix: Give every error code an HTTP status
Four rows of the error table name no HTTP status, so the `ErrorInfo`
constructor defaults them to 200. The `ripplerpc: "3.0"` envelope derives
the status from the code, so a reply reporting an error claims success
and anything reading the status, a load balancer above all, reads success
too. Give all four one: `actNotFound` answers 404, matching every
`*NotFound` sibling but `entryNotFound`, and `actMalformed`,
`alreadyMultisig` and `alreadySingleSig` answer 400. Then drop the
constructor that defaulted a status, so no row can omit one again. A gtest
lists by hand the codes that have no row, so an enumerator added without
one fails it, and asserts that every other code names a status other than
200.

No client reads a new status here. `legacyHttpStatus` reports 200 for
exactly those four rows, so the 3.0 envelope answers what it always has.
That list is closed, naming the rows that had no status of their own, so
a row added later reports whatever the table says. The test suite names
the two statuses it compares against most, 200 and 400, as `kOk` and
`kBadRequest`, and every existing assertion on them uses the name.
2026-10-25 12:06:11 -04:00
Bart 1f2a0d7991 style: Realign the error table
The columns of the error table had drifted apart as rows were added, so a reader scanning it follows a ragged edge and a new row has no alignment to copy. Realign all four columns on one set of widths inside the existing `clang-format off` guard, changing no row's content.
2026-10-25 12:06:11 -04:00
Bart 73611f8b20 fix: Mask every credential the server echoes, logs or prints
An error reply echoes the request that caused it, and masking covers four
fields at the top level of an object only. A `secret` nested inside
`params`, where the JSON-RPC transport puts it, comes back in the clear,
as does every other credential field at any depth, and only two sites
mask at all. Collect the names in one list and mask recursively, so
nesting stops mattering and a new field is added once. The list covers
the six names only a reply carries, which is how `wallet_propose` and
`validation_create` wrote a live key to the log; `validation_key` is the
same seed as `validation_seed` in RFC1751 words.

A log is an echo that outlives the reply, so one `loggable()` helper
masks and truncates together and every site that writes a request or a
reply out uses it, the `[rpc_startup]` command and its result included,
and the command line client logs the reply it receives parsed and
masked where it wrote the raw body, a `validation_create` answer among
them, and caps a body it cannot parse at the same length. An
`[rpc_startup]` line that does not parse was echoed raw at fatal, the
one case masking cannot reach, and then run as whatever the parser had
left; it is now reported by position only, since the parser's message
can quote a rejected number verbatim, and skipped. The `HTTP Reply` trace line in libxrpl, which cannot reach the
masker, now carries the status only; the body is logged beside it at
debug, masked when it carries a credential and otherwise as the string
already built for the wire, so a reply with no credential is serialized
once. No site logs an inbound request body at full length, so the method name,
at most the request size limit long, is the one thing a client chooses the
length of in the log. The request-duration line used to climb to warn
and error for a slow request with the request in it; the duration alone
still climbs, and the request stays at debug, so the duration line never
lifts text an anonymous client wrote to the default severity.

Three changes are visible to a caller. A credential in an echoed request
reads `<masked>` wherever it appears, nested inside `params` or a batch
entry too. The command line client masks `request_sent`, which carries
the `admin_password` it copies out of the config, so a failing
`./xrpld account_info rBogus` no longer prints a credential the operator
never typed. And a WebSocket frame that does not parse is answered with
its `size` rather than its body.
2026-10-25 12:06:10 -04:00
Bart 297f255988 refactor: Declare the JSON-RPC and ripplerpc version constants
`ripplerpc`, which selects the reply envelope and accepts three values,
and `jsonrpc`, which names the JSON-RPC protocol version, are spelled as
literals at every call site, so neither field has one place stating what
it accepts. Declare `kRippleRpcVersion1/2/3` beside the `api_version`
constants in ApiVersion.h and `kJsonRpcVersion` in a new
protocol/JsonRpc.h, and use them at every production site that reads
a value: the one in RPCCall.cpp that reads the pair, and the three in
ServerHandler.cpp that default, compare and gate on `ripplerpc`. Every
test site spelling a value as a C++ expression follows. A literal inside
a JSON string fixture is left alone, since substituting there means
assembling the JSON by concatenation. The three `ripplerpc` constants
are declared as one set, so the header states every value the field
accepts, and each has a production user in ServerHandler.cpp.

The two fields keep separate constants and separate headers although
both spell "2.0" today. The specification fixes `jsonrpc` at that value
while `ripplerpc` accepts three, and the `ripplerpc` constants go when
support for API versions 1 and 2 goes, where JSON-RPC is a protocol this
server keeps speaking.

ApiVersion.h's header comment named five constants by unprefixed
spellings that no longer exist, and read as a complete map of the file's
version constants, which it stops being here. Two jtx helpers,
`hasEnvelope2` and `setEnvelope2` in TestHelpers.h, replace the
assertion pair and the request pair that every rewritten test site
repeated. No value changes, on the wire or in a test.
2026-10-24 08:49:56 -04:00
Bart 89c01342c5 refactor: Let json::Value be compared and constructed from a string view
`json::Value` accepts `char const*`, `std::string` and
`json::StaticString`, so a caller holding a `std::string_view` has to
materialize a `std::string` whose characters are then copied a second
time into the value's own storage. Add the missing constructor, which
the `std::string` one delegates to, and an `operator==` that reads the
value's characters in place.

The comparison is constrained to `std::string_view` exactly rather than
taking a view by plain overload. A string literal converts equally well
to a view and to a Value, so a plain overload makes every
`value == "literal"` ambiguous, and a literal `0`, which converts to a
view through `char const*` as well as to a Value, with it.

No reply changes. The two spellings differ only for a view holding an
embedded NUL, which the Value comparison stops at. The `method: "batch"`
check in `ServerHandler.cpp` is the operator's first production user, so
that comparison stops building a `Value` from the literal on every
request.
2026-10-24 08:49:55 -04:00
Bart 8f49ff629d refactor: Remove a publish loop nothing can enter
`pubProposedAccountTransaction` declares an `accountHistoryNotify` vector,
never inserts into it, and then tests it and iterates it. The vector is a
local, so the proof is the function itself: between the declaration and the
loop there are exactly two mentions of it, the condition and the loop, and
neither adds an element. Its sibling `pubAccountTransaction` fills its own
copy, which is what makes the empty one here read as live code.

The compiler corroborates it: with the loop gone the message becomes
`MultiApiJson const`, which is possible only because that loop was its sole
mutator. Nothing is lost with the assertion above the loop either. It held
that a `transJson` result carries no member named `account_history_tx_stream`.
No code writes one: outside the two assertions, the token appears in the
`subscribe` and `unsubscribe` request handlers only. The identical assertion
stays where the loop it guards is live.

No subscriber sees a difference. An account-history subscription is
registered in `subAccountHistory_` alone, so it was never in the map this
function reads, and it receives its transactions from
`pubAccountTransaction`. The same function's guard on three subscription maps
also goes: an earlier return leaves `subRTAccount_` non-empty, so the
condition cannot be false.
2026-10-24 08:49:54 -04:00
Ayaz Salikhov 1f46f75318 build: Don't apply clang-tidy fixes twice via header symlinks (#8636) 2026-10-23 19:28:52 +00:00
262 changed files with 4380 additions and 1343 deletions

No files matched your search

-2
View File
@@ -51,8 +51,6 @@ IncludeBlocks: Regroup
IncludeCategories:
- Regex: "^<(test)/"
Priority: 1
- Regex: "^<(tests)/"
Priority: 1
- Regex: "^<(xrpld)/"
Priority: 2
- Regex: "^<(xrpl)/"
+5 -1
View File
@@ -140,5 +140,9 @@ CheckOptions:
readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$"
HeaderFilterRegex: '^.*/(tests?|xrpl|xrpld)/.*\.(h|hpp|ipp)$'
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$|^.*\.pb\.h$'
# Skip headers reached via add_module's symlinks (<build-dir>/modules/xrpl.lib*/...).
# verify_headers already checks each one under its real path,
# so reporting it twice would also apply its fixes twice.
# .ipp files are kept: they are only ever reached via the symlink.
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$|^.*\.pb\.h$|^.*/modules/xrpl\.lib[^/]+/.*\.(h|hpp)$'
WarningsAsErrors: "*"
-3
View File
@@ -14,9 +14,6 @@ endfunction()
function(xrpl_add_benchmark name)
endfunction()
function(xrpl_add_test_dir name)
endfunction()
macro(exclude_from_default target_)
endmacro()
@@ -175,8 +175,10 @@ test.server > xrpl.basics
test.server > xrpl.config
test.server > xrpld.app
test.server > xrpld.core
test.server > xrpld.rpc
test.server > xrpl.json
test.server > xrpl.protocol
test.server > xrpl.resource
test.server > xrpl.server
test.unit_test > xrpl.basics
test.unit_test > xrpl.protocol
+17
View File
@@ -22,6 +22,23 @@ API version 2 is available in `xrpld` version 2.0.0 and later. See [API-VERSION-
This version is supported by all `xrpld` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified.
## Unreleased
### Breaking changes
- A JSON string holding a NUL character, written as `\u0000` or as a raw byte, now makes the request unparsable, on every transport and at every API version, and is answered as any other body that does not parse. The server stored a string only through its first NUL, so `"ripplerpc": "3.0\u0000junk"` was read as `"3.0"` and any other field was cut short the same way. The two configuration sections read by the same parser, `[rpc_startup]` and `[validator_token]`, refuse it too.
- The `ripplerpc` request field, which selects the shape of the JSON-RPC reply envelope, is now validated, and a value that is not exactly `"1.0"`, `"2.0"` or `"3.0"` is rejected. This reaches the JSON-RPC transport at every API version. It does not reach a WebSocket session, which reads the field only to echo it back. A request sending `"2"`, `" 2.0"`, `"2.00"`, `"02.0"`, `"2.0.0"`, `"10.0"` or any other text, such as `"abc"` or `"x2"`, gets a working reply today. After this ships, such a request sent alone gets HTTP 400 with `ripplerpc is not a supported version`, charged as a malformed request, and such an entry of a `"method": "batch"` body gets that error in its own reply while the batch answers 200. A client that spells the version loosely must therefore be corrected to one of the three exact values, or omit the field. Previously the value was compared as a string, which both accepted values that name no version and ordered multi-digit versions incorrectly: `"abc"` and `"x2"` sorted above `"3.0"` and so selected version 3, and `"10.0"` sorted below `"2.0"` and so selected version 1. Requests that send one of the three supported values, or omit the field, are unaffected.
### Bugfixes
- Every credential-bearing field is masked wherever a request or a reply is echoed, logged or printed: `admin_password`, `admin_user`, `master_key`, `master_seed`, `master_seed_hex`, `passphrase`, `password`, `secret`, `seed`, `seed_hex`, `url_password`, `url_username`, `username`, `validation_key`, `validation_private_key` and `validation_seed`. The one list applies to a request echoed back in an error reply and to every request and reply written to the log. Nesting no longer matters, so a credential inside `params` is masked too. Six of the sixteen names, `master_key`, `master_seed`, `master_seed_hex`, `validation_key`, `validation_private_key` and `validation_seed`, occur only in a reply, which is how `wallet_propose` and `validation_create` used to write a live private key to the log. The `id`, `jsonrpc`, `ripplerpc` and `api_version` members a legacy reply echoes from the request are masked the same way. The command line client's trace of a reply's header covers the header only, not the body bytes read with it. A request or reply written to the log is truncated at 10,000 characters. An `[rpc_startup]` line that does not parse is reported by its position in the section alone, never by its text or by the parser's message, and no command runs for it.
- The command line client no longer prints a credential the operator did not type. A failing command echoes the request it built under `request_sent`, which carries the `admin_password` the client copies out of `[port_rpc]` in the config, so `./xrpld account_info rBogus` printed that password to stdout and into any captured output. `request_sent` is now masked. The `rpc` member beside it, which echoes the arguments as they were typed, is unchanged. The command line client also no longer writes an unparsed `json` or `ripple_path_find` argument to its trace log before parsing it, where a `secret` inside that argument could not be masked; it logs the parsed request instead, masked. The reply it receives is logged the same way, parsed and masked, where the raw body was written before, a `validation_create` answer included.
- A WebSocket frame that does not parse, or exceeds the request size limit, is answered `{"type": "error", "error": "jsonInvalid", "size": <bytes>}`. The frame's body is reported by size rather than echoed back in a `value` member, since a body that does not parse has no fields to mask. A client that read `value` gets `size` instead.
- Four error codes that named no HTTP status of their own, and so answered 200 on a reply reporting an error, now name one: `actMalformed`, `alreadyMultisig` and `alreadySingleSig` answer 400, and `actNotFound` answers 404. **No shipped envelope reports these four.** A request sending `ripplerpc: "3.0"` still receives 200 for all four, as it always has, so `account_info` on a malformed account or one the ledger does not hold answers 200 exactly as before.
- `submit`, `simulate`, `transaction_entry`, `ledger_entry` and `ledger_accept`: Errors from these methods now include `error_code` and `error_message` alongside the `error` token, as every other method already did. Each error now answers the status its code names: 400 for a malformed request, 404 for `transactionNotFound`, 500 for an internal failure, and 501 for `notYetImplemented` and `notStandAlone`. That status change reaches only a request sending `ripplerpc: "3.0"`, which is the envelope that derives the status from the error. With `ripplerpc` `"1.0"` the status stays 200 and the two new members appear beside `error`; with `"2.0"` the status stays 200, `error_code` appears, and the `code` and `message` members carry the code and the message rather than null, since that envelope copies them from `error_code` and `error_message` and drops `error_message`.
- A reply reporting HTTP 402 or 502 now carries a status line. Those two statuses named no case in the switch that writes one, so such a reply began with a header instead and did not parse as an HTTP response at all. Both are reachable at any API version with `ripplerpc: "3.0"`, which derives the status from the error code: 402 through `highFee` from `sign` or `submit` with a low `fee_mult_max`, and 502 through `dbDeserialization` from `tx`. The eleven statuses that already named a case report the same phrase they always have.
- An error reply to a request sending `ripplerpc: "2.0"` or `"3.0"` no longer carries a stray `"error_message": null` beside the error it reports. The member appeared only when the `Server` log partition was set to debug or lower, because the log statement read `error_message` after the reply had renamed it to `message`, and reading it put it back as null. So the reply a client received depended on the server's log level, and the log line itself printed an empty message. Both are fixed.
## XRP Ledger server version 3.5.0
Version 3.5.0 is not yet released.
+4 -3
View File
@@ -365,14 +365,15 @@ With this set, the hook will run as part of `git commit` alongside the other pre
Then run clang-tidy on your local changes:
```
run-clang-tidy -p build -allow-no-checks src tests
run-clang-tidy -p build -allow-no-checks include src tests
```
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory.
This will check all source files in the `include`, `src` and `tests` directories using the compile commands from your `build` directory.
Headers are checked on their own, which requires the `verify_headers` CMake option (ON by default).
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix -format` to the above command:
```
run-clang-tidy -p build -quiet -fix -format -allow-no-checks src tests
run-clang-tidy -p build -quiet -fix -format -allow-no-checks include src tests
```
`-format` reformats the fixed code with [`.clang-format`](./.clang-format); without it the fixes are inserted in LLVM style and the `clang-format` hook rewrites them afterwards.
+15 -66
View File
@@ -5,15 +5,6 @@ The script determines the staged files itself (see `pass_filenames: false` in
.pre-commit-config.yaml) so run-clang-tidy is run once and handles parallelism
internally: pre-commit would otherwise split the files across parallel hook
invocations that race when fixes edit a shared header.
Fixes are collected with `-export-fixes` and applied by clang-apply-replacements
in a separate step rather than with run-clang-tidy's `-fix`. The `add_module`
build isolates each module's headers behind a per-module symlink directory
(build/modules/<module>/...), so a header reachable from several translation
units is referenced through different paths that all resolve to the same source
file. clang-apply-replacements deduplicates identical replacements by their
literal path, so those paths must be canonicalised to the real source path
first; otherwise the same fix is applied once per path and corrupts the header.
"""
from __future__ import annotations
@@ -23,7 +14,6 @@ import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
# Extensions run-clang-tidy can analyse: `.cpp` translation units and, thanks to
@@ -31,11 +21,6 @@ from pathlib import Path
# compile_commands.json entry). `.ipp` fragments have no entry and are skipped.
TIDY_EXTENSIONS = {".cpp", ".h", ".hpp"}
# A single-quoted `FilePath:` entry in an -export-fixes YAML file, allowing the
# `- ` marker that precedes it inside a `Replacements:` sequence. clang-tidy
# emits paths single-quoted and doubles any embedded quote per YAML rules.
FILEPATH_RE = re.compile(r"^(\s*(?:-\s+)?FilePath:\s*)'((?:[^']|'')*)'\s*$")
def clang_tidy_major() -> str | None:
"""Major version of the `clang-tidy` on PATH, which run-clang-tidy invokes."""
@@ -79,25 +64,6 @@ def staged_files(repo_root: Path) -> list[Path]:
return [repo_root / rel for rel in output.splitlines() if rel]
def canonicalize_fix_paths(fixes_dir: Path) -> None:
"""Rewrite every `FilePath` in the exported fixes to its real source path.
A header included through a module's isolation symlink is recorded under that
symlink's path; collapsing all paths to the same real file lets
clang-apply-replacements recognise the per-translation-unit duplicates and
apply each fix once.
"""
for yaml in fixes_dir.glob("*.yaml"):
lines = []
for line in yaml.read_text().splitlines():
if m := FILEPATH_RE.match(line):
path = m.group(2).replace("''", "'")
real = os.path.realpath(path).replace("'", "''")
line = f"{m.group(1)}'{real}'"
lines.append(line)
yaml.write_text("\n".join(lines) + "\n")
def main():
if not os.environ.get("TIDY"):
return 0
@@ -116,19 +82,10 @@ def main():
version = clang_tidy_major()
run_clang_tidy = find_tool("run-clang-tidy", version)
clang_apply_replacements = find_tool("clang-apply-replacements", version)
missing = [
name
for name, path in (
("run-clang-tidy", run_clang_tidy),
("clang-apply-replacements", clang_apply_replacements),
)
if not path
]
if missing:
if not run_clang_tidy:
tried = f" (tried the '-{version}' suffix too)" if version else ""
print(
f"clang-tidy check failed: TIDY is enabled but {' and '.join(missing)} "
f"clang-tidy check failed: TIDY is enabled but run-clang-tidy "
f"was not found in PATH{tried}.",
file=sys.stderr,
)
@@ -143,27 +100,19 @@ def main():
)
return 1
with tempfile.TemporaryDirectory() as fixes_dir:
result = subprocess.run(
[
run_clang_tidy,
"-quiet",
"-p",
build_dir,
"-export-fixes",
fixes_dir,
"-allow-no-checks",
]
+ files
)
canonicalize_fix_paths(Path(fixes_dir))
# `FormatStyle` in .clang-tidy does not reach this path,
# so ask for the repository style here.
applied = subprocess.run(
[clang_apply_replacements, "--format", "--style=file", fixes_dir]
)
return result.returncode or applied.returncode
result = subprocess.run(
[
run_clang_tidy,
"-quiet",
"-p",
build_dir,
"-fix",
"-format",
"-allow-no-checks",
]
+ files
)
return result.returncode
if __name__ == "__main__":
+2 -1
View File
@@ -34,11 +34,12 @@ include(create_symbolic_link)
# sometimes `PUBLIC` and sometimes `PRIVATE`.
# The Cs are all guaranteed to be unique.
# We can guarantee a unique X per target by using
# `${CMAKE_CURRENT_BINARY_DIR}/include/${target}`.
# `${CMAKE_CURRENT_BINARY_DIR}/modules/${target}`.
#
# isolate_headers(target A B scope)
function(isolate_headers target A B scope)
file(RELATIVE_PATH C "${A}" "${B}")
# Keep in sync with ExcludeHeaderFilterRegex in .clang-tidy.
set(X "${CMAKE_CURRENT_BINARY_DIR}/modules/${target}")
set(Y "${X}/${C}")
cmake_path(GET Y PARENT_PATH parent)
@@ -16,7 +16,7 @@
#include <gtest/gtest.h>
#include <tests/libxrpl/protocol_autogen/TestHelpers.h>
#include <protocol_autogen/TestHelpers.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol_autogen/ledger_entries/${name}.h>
@@ -15,7 +15,7 @@
#include <gtest/gtest.h>
#include <tests/libxrpl/protocol_autogen/TestHelpers.h>
#include <protocol_autogen/TestHelpers.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Seed.h>
+10 -16
View File
@@ -29,19 +29,6 @@
#
# verify_target_headers(target headers_dir)
function(verify_target_headers target headers_dir)
# .ipp files are compiled through the header or translation unit that
# includes them, not on their own.
file(
GLOB_RECURSE headers
CONFIGURE_DEPENDS
"${headers_dir}/*.h"
"${headers_dir}/*.hpp"
)
# A directory without headers has nothing to verify, and an object library
# without sources is an error.
if(NOT headers)
return()
endif()
set(verify ${target}.verify)
if(NOT TARGET ${verify})
add_library(${verify} OBJECT EXCLUDE_FROM_ALL)
@@ -67,11 +54,18 @@ function(verify_target_headers target headers_dir)
)
add_dependencies(verify-headers ${verify})
endif()
_verify_add_headers(${verify} "${headers}")
_verify_add_headers(${verify} "${headers_dir}")
endfunction()
# Add the given headers to target as directly-compiled C++ translation units.
function(_verify_add_headers target headers)
# Add every .h/.hpp under dir to target as a directly-compiled C++ translation
# unit. .ipp files are inline-implementation fragments included by their owning
# header (often after a class declaration), so they are not self-contained on
# their own and are verified transitively when that header is verified.
function(_verify_add_headers target dir)
file(GLOB_RECURSE headers CONFIGURE_DEPENDS "${dir}/*.h" "${dir}/*.hpp")
if(NOT headers)
return()
endif()
# `-xc++` forces the header to be compiled as a C++ translation unit; a lone
# `.h` is otherwise treated as a header to precompile. `#pragma once` is
# harmless (and warns) when the header is the main file, so silence it.
+2 -2
View File
@@ -897,7 +897,7 @@ Consensus<Adaptor>::gotTxSet(NetClock::time_point const& now, TxSetT const& txSe
now_ = now;
auto const id = txSet.id();
auto id = txSet.id();
// If we've already processed this transaction set since requesting
// it from the network, there is nothing to do now
@@ -1624,7 +1624,7 @@ Consensus<Adaptor>::updateOurPositions(std::unique_ptr<std::stringstream> const&
if (ourNewSet)
{
auto const newID = ourNewSet->id();
auto newID = ourNewSet->id();
result_->txns = std::move(*ourNewSet);
+37
View File
@@ -3,11 +3,13 @@
#include <xrpl/basics/Number.h>
#include <xrpl/json/json_forwards.h>
#include <concepts>
#include <cstring>
#include <iterator>
#include <limits>
#include <map>
#include <string>
#include <string_view>
#include <vector>
/**
@@ -198,6 +200,15 @@ public:
*/
Value(StaticString const& value);
Value(std::string const& value);
/**
* @brief Constructs a value from a string view.
*
* The characters are copied, so the view need not outlive the call and need
* not be NUL-terminated.
*
* @param value The characters to copy.
*/
Value(std::string_view value);
Value(bool value);
Value(Value const& other);
~Value();
@@ -472,6 +483,32 @@ toJson(xrpl::Number const& number)
bool
operator==(Value const&, Value const&);
/**
* Compares a value with a string view, reading the value's characters in place
* rather than building a Value from the view.
*
* Constrained to the exact type: a string literal converts equally well to a
* view and to a Value, so a plain overload makes `value == "literal"`
* ambiguous.
*
* @param x The value to compare.
* @param y The characters to compare it against.
* @return Whether `x` is a string whose characters up to its first NUL are
* exactly the characters of `y`.
*/
template <class T>
requires std::same_as<T, std::string_view>
bool
operator==(Value const& x, T y)
{
if (!x.isString())
return false;
// A string `Value` can hold a null pointer, which names no characters, so it equals no view.
char const* const s = x.asCString();
return s != nullptr && std::string_view{s} == y;
}
bool
operator<(Value const&, Value const&);
+17 -17
View File
@@ -6,31 +6,21 @@
#include <xrpl/protocol/jss.h>
#include <cstddef>
#include <string_view>
#include <type_traits>
#include <utility>
namespace xrpl {
/**
* API version numbers used in later API versions
* The `api_version` numbers this server serves.
*
* Requests with a version number in the range
* [apiMinimumSupportedVersion, apiMaximumSupportedVersion]
* are supported.
* A request naming a version in [kApiMinimumSupportedVersion,
* kApiMaximumSupportedVersion] is served. With `[beta_rpc_api]` set to `1` in
* the config the range extends to kApiBetaVersion.
*
* If [beta_rpc_api] is enabled in config, the version numbers
* in the range [apiMinimumSupportedVersion, apiBetaVersion]
* are supported.
*
* Network Requests without explicit version numbers use
* apiVersionIfUnspecified. apiVersionIfUnspecified is 1,
* because all the RPC requests with a version >= 2 must
* explicitly specify the version in the requests.
* Note that apiVersionIfUnspecified will be lower than
* apiMinimumSupportedVersion when we stop supporting API
* version 1.
*
* Command line Requests use apiCommandLineVersion.
* A request naming no version is served at kApiVersionIfUnspecified, which is 1
* because a request wanting any later version states it.
*/
namespace rpc {
@@ -57,6 +47,16 @@ static_assert(kApiMaximumSupportedVersion >= kApiMinimumSupportedVersion);
static_assert(kApiBetaVersion >= kApiMaximumSupportedVersion);
static_assert(kApiMaximumValidVersion >= kApiMaximumSupportedVersion);
/**
* Values accepted in the `ripplerpc` request field, which selects the shape of
* the JSON-RPC reply envelope. Distinct from `kJsonRpcVersion` in JsonRpc.h,
* which names the JSON-RPC protocol itself, and from the `api_version`
* constants above, which select the content of the response.
*/
inline constexpr std::string_view kRippleRpcVersion1{"1.0"};
inline constexpr std::string_view kRippleRpcVersion2{"2.0"};
inline constexpr std::string_view kRippleRpcVersion3{"3.0"};
inline void
setVersion(json::Value& parent, unsigned int apiVersion, bool betaEnabled)
{
+21 -6
View File
@@ -144,7 +144,27 @@ enum ErrorCodeI {
RpcEntryNotFound = 98,
RpcUnexpectedLedgerType = 99,
RpcLast = RpcUnexpectedLedgerType, // rpcLAST should always equal the last code.
// submit + simulate
RpcInvalidTransaction = 100,
RpcInternalSubmit = 101,
RpcInternalJson = 102,
RpcInternalSimulate = 103,
// transaction_entry
RpcFieldNotFoundTransaction = 104,
RpcNotYetImplemented = 105,
RpcTransactionNotFound = 106,
// transaction_entry
RpcMalformedRequest = 107,
// ledger_accept
RpcNotStandAlone = 108,
// ledger_entry, API version 1 only
RpcUnknownOption = 109,
RpcLast = RpcUnknownOption, // rpcLAST should always equal the last code.
};
/**
@@ -180,11 +200,6 @@ struct ErrorInfo
{
}
constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message)
: code(code), token(token), message(message), httpStatus(200)
{
}
constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message, int httpStatus)
: code(code), token(token), message(message), httpStatus(httpStatus)
{
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <xrpl/json/json_forwards.h>
#include <string_view>
namespace xrpl::rpc {
/**
* Constants of the JSON-RPC 2.0 protocol itself.
*
* Kept apart from the `api_version` and `ripplerpc` constants in ApiVersion.h,
* which go when support for API versions 1 and 2 goes.
*/
/**
* Value of the `jsonrpc` member of a request and of its reply, fixed at "2.0"
* by the JSON-RPC specification.
*/
inline constexpr std::string_view kJsonRpcVersion{"2.0"};
/**
* Codes for the `code` member of a JSON-RPC error object.
*
* The specification reserves -32768 to -32000 for the protocol and leaves
* -32000 to -32099 of it to the implementation.
*
* kJsonRpcServerError is the code for an error an XRPL handler reports.
*
* The codes from kJsonRpcServerOverloaded on lie outside the
* implementation-defined sub-range, which the specification does not allow.
* They are the codes every shipped version reports, so moving one breaks the
* clients matching on it.
*/
inline constexpr json::Int kJsonRpcServerError{-32000};
inline constexpr json::Int kJsonRpcInvalidRequest{-32600};
inline constexpr json::Int kJsonRpcMethodNotFound{-32601};
inline constexpr json::Int kJsonRpcInvalidParams{-32602};
inline constexpr json::Int kJsonRpcServerOverloaded{-32604};
inline constexpr json::Int kJsonRpcForbidden{-32605};
inline constexpr json::Int kJsonRpcWrongVersion{-32606};
} // namespace xrpl::rpc
+2 -2
View File
@@ -244,7 +244,7 @@ JSS(ephemeral_key); // out: ValidatorInfo
JSS(error); // out: error
JSS(errored); //
JSS(error_code); // out: error
JSS(error_exception); // out: Submit
JSS(error_exception); // out: Submit, Simulate
JSS(error_message); // out: error
JSS(expand); // in: handler/Ledger
JSS(expected_date); // out: any (warnings)
@@ -560,7 +560,7 @@ JSS(signing_key); // out: NetworkOPs
JSS(signing_keys); // out: ValidatorList
JSS(signing_time); // out: NetworkOPs
JSS(signer_lists); // in/out: AccountInfo
JSS(size); // out: get_aggregate_price
JSS(size); // out: get_aggregate_price, ServerHandler
JSS(snapshot); // in: Subscribe
JSS(source_account); // in: PathRequest, RipplePathFind
JSS(source_amount); // in: PathRequest, RipplePathFind
+14
View File
@@ -7,6 +7,20 @@
namespace xrpl {
/**
* Writes an HTTP reply carrying @p strMsg with status @p nStatus to @p output,
* and logs the status at trace. The body is not logged here: it may carry a
* credential this library cannot mask, so the caller logs it masked.
*
* A 401 with an empty body is answered with the fixed authentication page.
* The status line carries the phrase Beast's registry gives @p nStatus, except
* for 401 and 503, which carry a phrase of this server's own.
*
* @param nStatus The HTTP status code.
* @param strMsg The body.
* @param output Where the reply bytes are written.
* @param j The journal the status is logged to.
*/
void
httpReply(int nStatus, std::string const& strMsg, json::Output const&, beast::Journal j);
-1
View File
@@ -144,7 +144,6 @@ public:
XRPL_ASSERT(
view_.has_value(),
"xrpl::ApplyContext::getApplyViewContext : view_ emplaced in constructor");
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
return {.view = *view_, .tx = tx};
}
+2 -2
View File
@@ -62,8 +62,8 @@ let
# version-suffixed name first and fall back to a system binary on the PATH
# when Nix doesn't provide it:
# - Conan's Boost recipe looks up `g++-<major>` before plain `g++`.
# - bin/pre-commit/clang_tidy_check.py looks up `run-clang-tidy-<v>` and
# `clang-apply-replacements-<v>` before the unsuffixed names.
# - bin/pre-commit/clang_tidy_check.py looks up `run-clang-tidy-<v>`
# before the unsuffixed name.
# On a host that also has the matching system binary
# (e.g. Ubuntu's `/usr/bin/g++-<v>` or `clang-tidy-<v>`),
# the probe escapes Nix and mixes a system tool into the Nix environment.
+10
View File
@@ -703,6 +703,11 @@ Reader::decodeString(Token& token, std::string& decoded)
if (!decodeUnicodeCodePoint(token, current, end, unicode))
return false;
// json::Value stores a string through its first NUL, so a string holding one
// would be read as the text before it. Refuse it rather than truncate.
if (unicode == 0)
return addError("Null character in string", token, current);
decoded += codePointToUTF8(unicode);
}
break;
@@ -711,6 +716,11 @@ Reader::decodeString(Token& token, std::string& decoded)
return addError("Bad escape sequence in string", token, current);
}
}
else if (c == '\0')
{
// The raw form of the escape refused above.
return addError("Null character in string", token, current);
}
else
{
decoded += c;
+8 -3
View File
@@ -15,6 +15,7 @@
#include <cstring>
#include <limits>
#include <string>
#include <string_view>
#include <utility>
namespace json {
@@ -241,10 +242,14 @@ Value::Value(xrpl::Number const& value) : type_(ValueType::String), allocated_(t
value_.stringVal = valueAllocator()->duplicateStringValue(tmp.c_str(), tmp.length());
}
Value::Value(std::string const& value) : type_(ValueType::String), allocated_(true)
Value::Value(std::string const& value) : Value(std::string_view{value})
{
value_.stringVal =
valueAllocator()->duplicateStringValue(value.c_str(), (unsigned int)value.length());
}
Value::Value(std::string_view value) : type_(ValueType::String), allocated_(true)
{
value_.stringVal = valueAllocator()->duplicateStringValue(
value.data(), static_cast<unsigned int>(value.length()));
}
Value::Value(StaticString const& value) : type_(ValueType::String)
+12 -1
View File
@@ -30,6 +30,7 @@
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
namespace xrpl {
@@ -363,12 +364,22 @@ public:
}
}
/**
* Reads the status line and headers of the reply, then the body.
*
* @param ecResult The outcome of the read that delivered the header.
* @param bytesTransferred The length of the header through its `\r\n\r\n`
* delimiter; the buffer can hold body bytes read past it.
*/
void
handleHeader(boost::system::error_code const& ecResult, std::size_t bytesTransferred)
{
std::string strHeader{
{std::istreambuf_iterator<char>(&header_)}, std::istreambuf_iterator<char>()};
JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
// The trace covers the header only: the bytes read past the delimiter are body, which can
// carry a credential.
JLOG(j_.trace()) << "Header: \"" << std::string_view{strHeader}.substr(0, bytesTransferred)
<< "\"";
static boost::regex const kReStatus{R"(\`HTTP/1\S+ (\d{3}) .*\')"}; // HTTP/1.1 200 OK
static boost::regex const kReSize{
+88 -74
View File
@@ -20,88 +20,102 @@ namespace detail {
// will remain in the object file. But the string literals will remain.
//
// There's a certain amount of tension in determining the correct HTTP
// status to associate with a given RPC error. Initially all RPC errors
// returned 200 (OK). And that's the default behavior if no HTTP status code
// is specified below.
// status to associate with a given RPC error. Every row below names its
// HTTP status, and a row that omits it does not compile, since ErrorInfo has
// no constructor that leaves the status out.
//
// The codes currently selected target the load balancer fail-over use case.
// If a query fails on one node but is likely to have a positive outcome
// on a different node, then the failure should return a 4xx/5xx range
// status code.
// The rows below are aligned by hand into four columns, so that a wrong status or a mistyped token
// is visible by scanning one column. That cannot be had inside the 100-column limit once the
// longest enumerator and the longest message sit in one row. So this region is exempt from the
// limit by design rather than by oversight.
// clang-format off
static constexpr ErrorInfo kUnorderedErrorInfos[]{
{RpcActMalformed, "actMalformed", "Account malformed."},
{RpcActNotFound, "actNotFound", "Account not found."},
{RpcAlreadyMultisig, "alreadyMultisig", "Already multisigned."},
{RpcAlreadySingleSig, "alreadySingleSig", "Already single-signed."},
{RpcAmendmentBlocked, "amendmentBlocked", "Amendment blocked, need upgrade.", 503},
{RpcExpiredValidatorList, "unlBlocked", "Validator list expired.", 503},
{RpcAtxDeprecated, "deprecated", "Use the new API or specify a ledger range.", 400},
{RpcBadKeyType, "badKeyType", "Bad key type.", 400},
{RpcBadFeature, "badFeature", "Feature unknown or invalid.", 500},
{RpcBadIssuer, "badIssuer", "Issuer account malformed.", 400},
{RpcBadMarket, "badMarket", "No such market.", 404},
{RpcBadSecret, "badSecret", "Secret does not match account.", 403},
{RpcBadSeed, "badSeed", "Disallowed seed.", 403},
{RpcBadSyntax, "badSyntax", "Syntax error.", 400},
{RpcChannelMalformed, "channelMalformed", "Payment channel is malformed.", 400},
{RpcChannelAmtMalformed, "channelAmtMalformed", "Payment channel amount is malformed.", 400},
{RpcCommandMissing, "commandMissing", "Missing command entry.", 400},
{RpcDbDeserialization, "dbDeserialization", "Database deserialization error.", 502},
{RpcDstActMalformed, "dstActMalformed", "Destination account is malformed.", 400},
{RpcDstActMissing, "dstActMissing", "Destination account not provided.", 400},
{RpcDstActNotFound, "dstActNotFound", "Destination account not found.", 404},
{RpcDstAmtMalformed, "dstAmtMalformed", "Destination amount/currency/issuer is malformed.", 400},
{RpcDstAmtMissing, "dstAmtMissing", "Destination amount/currency/issuer is missing.", 400},
{RpcDstIsrMalformed, "dstIsrMalformed", "Destination issuer is malformed.", 400},
{RpcExcessiveLgrRange, "excessiveLgrRange", "Ledger range exceeds 1000.", 400},
{RpcForbidden, "forbidden", "Bad credentials.", 403},
{RpcHighFee, "highFee", "Current transaction fee exceeds your limit.", 402},
{RpcInternal, "internal", "Internal error.", 500},
{RpcInvalidLgrRange, "invalidLgrRange", "Ledger range is invalid.", 400},
{RpcInvalidParams, "invalidParams", "Invalid parameters.", 400},
{RpcInvalidHotwallet, "invalidHotWallet", "Invalid hotwallet.", 400},
{RpcIssueMalformed, "issueMalformed", "Issue is malformed.", 400},
{RpcJsonRpc, "json_rpc", "JSON-RPC transport error.", 500},
{RpcLgrIdxsInvalid, "lgrIdxsInvalid", "Ledger indexes invalid.", 400},
{RpcLgrIdxMalformed, "lgrIdxMalformed", "Ledger index malformed.", 400},
{RpcLgrNotFound, "lgrNotFound", "Ledger not found.", 404},
{RpcLgrNotValidated, "lgrNotValidated", "Ledger not validated.", 202},
{RpcMasterDisabled, "masterDisabled", "Master key is disabled.", 403},
{RpcNotEnabled, "notEnabled", "Not enabled in configuration.", 501},
{RpcNotImpl, "notImpl", "Not implemented.", 501},
{RpcNotReady, "notReady", "Not ready to handle this request.", 503},
{RpcNotSupported, "notSupported", "Operation not supported.", 501},
{RpcNoClosed, "noClosed", "Closed ledger is unavailable.", 503},
{RpcNoCurrent, "noCurrent", "Current ledger is unavailable.", 503},
{RpcNotSynced, "notSynced", "Not synced to the network.", 503},
{RpcNoEvents, "noEvents", "Current transport does not support events.", 405},
{RpcNoNetwork, "noNetwork", "Not synced to the network.", 503},
{RpcWrongNetwork, "wrongNetwork", "Wrong network.", 503},
{RpcNoPermission, "noPermission", "You don't have permission for this command.", 401},
{RpcNoPfRequest, "noPathRequest", "No pathfinding request in progress.", 404},
{RpcObjectNotFound, "objectNotFound", "The requested object was not found.", 404},
{RpcPublicMalformed, "publicMalformed", "Public key is malformed.", 400},
{RpcSendmaxMalformed, "sendMaxMalformed", "SendMax amount malformed.", 400},
{RpcSigningMalformed, "signingMalformed", "Signing of transaction is malformed.", 400},
{RpcSlowDown, "slowDown", "You are placing too much load on the server.", 429},
{RpcSrcActMalformed, "srcActMalformed", "Source account is malformed.", 400},
{RpcSrcActMissing, "srcActMissing", "Source account not provided.", 400},
{RpcSrcActNotFound, "srcActNotFound", "Source account not found.", 404},
{RpcDelegateActNotFound, "delegateActNotFound", "Delegate account not found.", 404},
{RpcSrcCurMalformed, "srcCurMalformed", "Source currency is malformed.", 400},
{RpcSrcIsrMalformed, "srcIsrMalformed", "Source issuer is malformed.", 400},
{RpcStreamMalformed, "malformedStream", "Stream malformed.", 400},
{RpcTooBusy, "tooBusy", "The server is too busy to help you now.", 503},
{RpcTxnNotFound, "txnNotFound", "Transaction not found.", 404},
{RpcUnknownCommand, "unknownCmd", "Unknown method.", 405},
{RpcOracleMalformed, "oracleMalformed", "Oracle request is malformed.", 400},
{RpcBadCredentials, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
{RpcTxSigned, "transactionSigned", "Transaction should not be signed.", 400},
{RpcDomainMalformed, "domainMalformed", "Domain is malformed.", 400},
{RpcEntryNotFound, "entryNotFound", "Entry not found.", 400},
{RpcUnexpectedLedgerType, "unexpectedLedgerType", "Unexpected ledger type.", 400},
{RpcActMalformed, "actMalformed", "Account malformed.", 400},
{RpcActNotFound, "actNotFound", "Account not found.", 404},
{RpcAlreadyMultisig, "alreadyMultisig", "Already multisigned.", 400},
{RpcAlreadySingleSig, "alreadySingleSig", "Already single-signed.", 400},
{RpcAmendmentBlocked, "amendmentBlocked", "Amendment blocked, need upgrade.", 503},
{RpcExpiredValidatorList, "unlBlocked", "Validator list expired.", 503},
{RpcAtxDeprecated, "deprecated", "Use the new API or specify a ledger range.", 400},
{RpcBadKeyType, "badKeyType", "Bad key type.", 400},
{RpcBadFeature, "badFeature", "Feature unknown or invalid.", 500},
{RpcBadIssuer, "badIssuer", "Issuer account malformed.", 400},
{RpcBadMarket, "badMarket", "No such market.", 404},
{RpcBadSecret, "badSecret", "Secret does not match account.", 403},
{RpcBadSeed, "badSeed", "Disallowed seed.", 403},
{RpcBadSyntax, "badSyntax", "Syntax error.", 400},
{RpcChannelMalformed, "channelMalformed", "Payment channel is malformed.", 400},
{RpcChannelAmtMalformed, "channelAmtMalformed", "Payment channel amount is malformed.", 400},
{RpcCommandMissing, "commandMissing", "Missing command entry.", 400},
{RpcDbDeserialization, "dbDeserialization", "Database deserialization error.", 502},
{RpcDstActMalformed, "dstActMalformed", "Destination account is malformed.", 400},
{RpcDstActMissing, "dstActMissing", "Destination account not provided.", 400},
{RpcDstActNotFound, "dstActNotFound", "Destination account not found.", 404},
{RpcDstAmtMalformed, "dstAmtMalformed", "Destination amount/currency/issuer is malformed.", 400},
{RpcDstAmtMissing, "dstAmtMissing", "Destination amount/currency/issuer is missing.", 400},
{RpcDstIsrMalformed, "dstIsrMalformed", "Destination issuer is malformed.", 400},
{RpcExcessiveLgrRange, "excessiveLgrRange", "Ledger range exceeds 1000.", 400},
{RpcForbidden, "forbidden", "Bad credentials.", 403},
{RpcHighFee, "highFee", "Current transaction fee exceeds your limit.", 402},
{RpcInternal, "internal", "Internal error.", 500},
{RpcInvalidLgrRange, "invalidLgrRange", "Ledger range is invalid.", 400},
{RpcInvalidParams, "invalidParams", "Invalid parameters.", 400},
{RpcInvalidHotwallet, "invalidHotWallet", "Invalid hotwallet.", 400},
{RpcIssueMalformed, "issueMalformed", "Issue is malformed.", 400},
{RpcJsonRpc, "json_rpc", "JSON-RPC transport error.", 500},
{RpcLgrIdxsInvalid, "lgrIdxsInvalid", "Ledger indexes invalid.", 400},
{RpcLgrIdxMalformed, "lgrIdxMalformed", "Ledger index malformed.", 400},
{RpcLgrNotFound, "lgrNotFound", "Ledger not found.", 404},
{RpcLgrNotValidated, "lgrNotValidated", "Ledger not validated.", 202},
{RpcMasterDisabled, "masterDisabled", "Master key is disabled.", 403},
{RpcNotEnabled, "notEnabled", "Not enabled in configuration.", 501},
{RpcNotImpl, "notImpl", "Not implemented.", 501},
{RpcNotReady, "notReady", "Not ready to handle this request.", 503},
{RpcNotSupported, "notSupported", "Operation not supported.", 501},
{RpcNoClosed, "noClosed", "Closed ledger is unavailable.", 503},
{RpcNoCurrent, "noCurrent", "Current ledger is unavailable.", 503},
{RpcNotSynced, "notSynced", "Not synced to the network.", 503},
{RpcNoEvents, "noEvents", "Current transport does not support events.", 405},
{RpcNoNetwork, "noNetwork", "Not synced to the network.", 503},
{RpcWrongNetwork, "wrongNetwork", "Wrong network.", 503},
{RpcNoPermission, "noPermission", "You don't have permission for this command.", 401},
{RpcNoPfRequest, "noPathRequest", "No pathfinding request in progress.", 404},
{RpcObjectNotFound, "objectNotFound", "The requested object was not found.", 404},
{RpcPublicMalformed, "publicMalformed", "Public key is malformed.", 400},
{RpcSendmaxMalformed, "sendMaxMalformed", "SendMax amount malformed.", 400},
{RpcSigningMalformed, "signingMalformed", "Signing of transaction is malformed.", 400},
{RpcSlowDown, "slowDown", "You are placing too much load on the server.", 429},
{RpcSrcActMalformed, "srcActMalformed", "Source account is malformed.", 400},
{RpcSrcActMissing, "srcActMissing", "Source account not provided.", 400},
{RpcSrcActNotFound, "srcActNotFound", "Source account not found.", 404},
{RpcDelegateActNotFound, "delegateActNotFound", "Delegate account not found.", 404},
{RpcSrcCurMalformed, "srcCurMalformed", "Source currency is malformed.", 400},
{RpcSrcIsrMalformed, "srcIsrMalformed", "Source issuer is malformed.", 400},
{RpcStreamMalformed, "malformedStream", "Stream malformed.", 400},
{RpcTooBusy, "tooBusy", "The server is too busy to help you now.", 503},
{RpcTxnNotFound, "txnNotFound", "Transaction not found.", 404},
{RpcUnknownCommand, "unknownCmd", "Unknown method.", 405},
{RpcOracleMalformed, "oracleMalformed", "Oracle request is malformed.", 400},
{RpcBadCredentials, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
{RpcTxSigned, "transactionSigned", "Transaction should not be signed.", 400},
{RpcDomainMalformed, "domainMalformed", "Domain is malformed.", 400},
{RpcEntryNotFound, "entryNotFound", "Entry not found.", 400},
{RpcUnexpectedLedgerType, "unexpectedLedgerType", "Unexpected ledger type.", 400},
{RpcInvalidTransaction, "invalidTransaction", "Transaction is invalid.", 400},
{RpcInternalSubmit, "internalSubmit", "Internal error during submit.", 500},
{RpcInternalJson, "internalJson", "Internal error during JSON handling.", 500},
{RpcInternalSimulate, "internalSimulate", "Internal error during simulate.", 500},
{RpcFieldNotFoundTransaction, "fieldNotFoundTransaction", "Missing required field.", 400},
{RpcNotYetImplemented, "notYetImplemented", "Not yet implemented.", 501},
{RpcTransactionNotFound, "transactionNotFound", "Transaction not found.", 404},
{RpcMalformedRequest, "malformedRequest", "Request is malformed.", 400},
{RpcNotStandAlone, "notStandAlone", "Server is not running stand-alone.", 501},
{RpcUnknownOption, "unknownOption", "Unknown option.", 400},
};
// clang-format on
+19 -26
View File
@@ -6,7 +6,10 @@
#include <xrpl/protocol/BuildInfo.h>
#include <xrpl/protocol/SystemParameters.h>
#include <boost/beast/http/status.hpp>
#include <ctime>
#include <format>
#include <string>
namespace xrpl {
@@ -33,7 +36,10 @@ getHTTPHeaderTimestamp()
void
httpReply(int nStatus, std::string const& content, json::Output const& output, beast::Journal j)
{
JLOG(j.trace()) << "HTTP Reply " << nStatus << " " << content;
// The status only. The body is a reply, which carries a credential when the
// command is a keygen, and this library cannot mask one: the caller logs the
// masked body at debug.
JLOG(j.trace()) << "HTTP Reply " << nStatus;
if (content.empty() && nStatus == 401)
{
@@ -69,42 +75,29 @@ httpReply(int nStatus, std::string const& content, json::Output const& output, b
return;
}
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (nStatus)
{
// The status every successful reply carries, so a literal rather than a format call.
case 200:
output("HTTP/1.1 200 OK\r\n");
break;
case 202:
output("HTTP/1.1 202 Accepted\r\n");
break;
case 400:
output("HTTP/1.1 400 Bad Request\r\n");
break;
// Two statuses this server phrases itself rather than taking from the registry.
case 401:
output("HTTP/1.1 401 Authorization Required\r\n");
break;
case 403:
output("HTTP/1.1 403 Forbidden\r\n");
break;
case 404:
output("HTTP/1.1 404 Not Found\r\n");
break;
case 405:
output("HTTP/1.1 405 Method Not Allowed\r\n");
break;
case 429:
output("HTTP/1.1 429 Too Many Requests\r\n");
break;
case 500:
output("HTTP/1.1 500 Internal Server Error\r\n");
break;
case 501:
output("HTTP/1.1 501 Not Implemented\r\n");
break;
case 503:
output("HTTP/1.1 503 Server is overloaded\r\n");
break;
default:
// A reply whose first line is a header is not an HTTP response. Beast knows the whole
// registry, so a status the error table gains needs no case here.
output(
std::format(
"HTTP/1.1 {} {}\r\n",
nStatus,
boost::beast::http::obsolete_reason(
static_cast<boost::beast::http::status>(nStatus))));
break;
}
output(getHTTPHeaderTimestamp());
+2 -1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <test/jtx/PeerStub.h>
#include <tests/libxrpl/shamap/DeepChain.h>
#include <xrpld/app/ledger/ConsensusTransSetSF.h>
#include <xrpld/overlay/Peer.h>
@@ -15,6 +14,8 @@
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <tests/libxrpl/shamap/DeepChain.h>
#include <xrpl.pb.h>
#include <atomic>
+2 -1
View File
@@ -2801,7 +2801,8 @@ class Batch_test : public beast::unit_test::Suite
auto const jrr = env.rpc("submit", strHex(slice))[jss::result];
expect(
jrr[jss::status] == "error" && jrr[jss::error] == "invalidTransaction" &&
!jrr.isMember(jss::error_code) && jrr[jss::error_message].isNull() &&
jrr[jss::error_code] == RpcInvalidTransaction &&
jrr[jss::error_message] == "Transaction is invalid." &&
jrr[jss::error_exception] ==
"fails local checks: Batch inner transactions are never "
"considered validly signed.",
+11 -10
View File
@@ -21,6 +21,7 @@
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -7944,8 +7945,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
// Signature verification fails — rejected before ZKP check
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
BEAST_EXPECT(jr[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jr[jss::result][jss::error_message] == "Transaction is invalid.");
}
// Variant B: Re-signed with inflated ciphertext.
@@ -8053,8 +8054,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
BEAST_EXPECT(jr[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jr[jss::result][jss::error_message] == "Transaction is invalid.");
}
// Variant B: Re-signed with all negated ciphertexts.
@@ -8158,8 +8159,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
BEAST_EXPECT(jr[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jr[jss::result][jss::error_message] == "Transaction is invalid.");
}
// Variant B: Re-signed with combined ciphertext.
@@ -8284,8 +8285,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
// Signature verification fails
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
BEAST_EXPECT(jr[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jr[jss::result][jss::error_message] == "Transaction is invalid.");
}
// Variant B: Re-signed C1 substitution.
@@ -8376,8 +8377,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
// signed — transaction rejected before ZKP verification.
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
BEAST_EXPECT(jr[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jr[jss::result][jss::error_message] == "Transaction is invalid.");
}
// -----------------------------------------------------------------
+2 -2
View File
@@ -1946,8 +1946,8 @@ class MPToken_test : public beast::unit_test::Suite
jtx.stx->add(s);
auto jrr = env.rpc("submit", strHex(s.slice()));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jrr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jrr[jss::result][jss::error_message].isNull());
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jrr[jss::result][jss::error_message] == "Transaction is invalid.");
// tx is unsigned
json::Value jv1;
+22 -22
View File
@@ -1080,8 +1080,8 @@ public:
local.setFieldVL(sfSigningPubKey, Blob()); // Empty SigningPubKey
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Empty SigningPubKey.");
@@ -1097,8 +1097,8 @@ public:
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid signature.");
@@ -1113,8 +1113,8 @@ public:
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid signature.");
@@ -1126,8 +1126,8 @@ public:
local[sfSigningPubKey] = alice.pk(); // Insert sfSigningPubKey
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Cannot both single- and multi-sign.");
@@ -1140,8 +1140,8 @@ public:
local.setFieldVL(sfSigningPubKey, Blob()); // Empty SigningPubKey
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Cannot both single- and multi-sign.");
@@ -1158,8 +1158,8 @@ public:
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception].asString().contains(
"Invalid signature on account r"));
@@ -1171,8 +1171,8 @@ public:
local.peekFieldArray(sfSigners).clear(); // Empty Signers array.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid Signers array size.");
@@ -1219,8 +1219,8 @@ public:
STTx const local = *tx.stx;
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid Signers array size.");
@@ -1231,8 +1231,8 @@ public:
STTx const local = *tx.stx;
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid multisigner.");
@@ -1243,8 +1243,8 @@ public:
STTx const local = *tx.stx;
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Duplicate Signers not allowed.");
@@ -1259,8 +1259,8 @@ public:
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Unsorted Signers array.");
+1 -2
View File
@@ -3286,8 +3286,7 @@ public:
BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
if (wsc->version() == 2)
{
BEAST_EXPECT(jrr.isMember(jss::jsonrpc) && jrr[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jrr.isMember(jss::ripplerpc) && jrr[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jrr));
BEAST_EXPECT(jrr.isMember(jss::id) && jrr[jss::id] == 5);
}
+1 -2
View File
@@ -2269,8 +2269,7 @@ public:
BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
if (wsc->version() == 2)
{
BEAST_EXPECT(jrr.isMember(jss::jsonrpc) && jrr[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jrr.isMember(jss::ripplerpc) && jrr[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jrr));
BEAST_EXPECT(jrr.isMember(jss::id) && jrr[jss::id] == 5);
}
+3 -2
View File
@@ -35,6 +35,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -1154,8 +1155,8 @@ class LoanBroker_test : public beast::unit_test::Suite
auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
// fails in doSubmit() on STTx construction
BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
BEAST_EXPECT(!jrr.isMember(jss::error_code));
BEAST_EXPECT(jrr[jss::error_message].isNull());
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jrr[jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
}
+3 -2
View File
@@ -20,6 +20,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/Indexes.h>
@@ -190,8 +191,8 @@ private:
BEAST_EXPECT(jr.isMember(jss::result));
auto const jResult = jr[jss::result];
BEAST_EXPECT(jResult[jss::error] == "invalidTransaction");
BEAST_EXPECT(!jResult.isMember(jss::error_code));
BEAST_EXPECT(jResult[jss::error_message].isNull());
BEAST_EXPECT(jResult[jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(jResult[jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
jResult[jss::error_exception] ==
"fails local checks: Transaction has bad signature.");
+4 -2
View File
@@ -23,6 +23,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -196,8 +197,9 @@ private:
jvResult[jss::tx_blob] = strHex(local.getSerializer().slice());
auto res = env.rpc("json", "submit", to_string(jvResult))["result"];
BEAST_EXPECT(
res[jss::error] == "invalidTransaction" && !res.isMember(jss::error_code) &&
res[jss::error_message].isNull() &&
res[jss::error] == "invalidTransaction" &&
res[jss::error_code] == RpcInvalidTransaction &&
res[jss::error_message] == "Transaction is invalid." &&
res[jss::error_exception] ==
"fails local checks: Counterparty: Invalid signature.");
});
+512
View File
@@ -0,0 +1,512 @@
#pragma once
#include <xrpld/app/consensus/RCLCxPeerPos.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/STValidation.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/server/InfoSub.h>
#include <xrpl/server/Manifest.h>
#include <xrpl/server/NetworkOPs.h>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
namespace xrpl::test {
/**
* A `NetworkOPs` whose every method is a no-op returning a default.
*
* It exists so a handler test can supply its own operations object through
* `rpc::Context::netOps`: derive from this and override only the methods the
* test cares about. Adding a method to `NetworkOPs` or `InfoSub::Source` then
* costs one stub here, not one per double.
*/
class NetworkOPsStub : public NetworkOPs
{
public:
NetworkOPsStub() = default;
~NetworkOPsStub() override = default;
void
stop() override
{
}
[[nodiscard]] OperatingMode
getOperatingMode() const override
{
return {};
}
[[nodiscard]] std::string
strOperatingMode(OperatingMode const, bool const) const override
{
return {};
}
[[nodiscard]] std::string
strOperatingMode(bool const) const override
{
return {};
}
void
submitTransaction(std::shared_ptr<STTx const> const&) override
{
}
void
processTransaction(std::shared_ptr<Transaction>&, bool, bool, FailHard) override
{
}
void
processTransactionSet(CanonicalTXSet const&) override
{
}
json::Value
getOwnerInfo(std::shared_ptr<ReadView const>, AccountID const&) override
{
return {};
}
void
getBookPage(
std::shared_ptr<ReadView const>&,
Book const&,
AccountID const&,
bool const,
unsigned int,
json::Value const&,
json::Value&) override
{
}
bool
processTrustedProposal(RCLCxPeerPos) override
{
return {};
}
bool
recvValidation(std::shared_ptr<STValidation> const&, std::string const&) override
{
return {};
}
void
mapComplete(std::shared_ptr<SHAMap> const&, bool) override
{
}
bool
beginConsensus(UInt256 const&, std::unique_ptr<std::stringstream> const&) override
{
return {};
}
void
endConsensus(std::unique_ptr<std::stringstream> const&) override
{
}
void
setStandAlone() override
{
}
void
setStateTimer() override
{
}
void
setNeedNetworkLedger() override
{
}
void
clearNeedNetworkLedger() override
{
}
bool
isNeedNetworkLedger() override
{
return {};
}
bool
isFull() override
{
return {};
}
void
setMode(OperatingMode) override
{
}
bool
isBlocked() override
{
return {};
}
bool
isAmendmentBlocked() override
{
return {};
}
void
setAmendmentBlocked() override
{
}
bool
isAmendmentWarned() override
{
return {};
}
void
setAmendmentWarned() override
{
}
void
clearAmendmentWarned() override
{
}
bool
isUNLBlocked() override
{
return {};
}
void
setUNLBlocked() override
{
}
void
clearUNLBlocked() override
{
}
void
consensusViewChange() override
{
}
json::Value
getConsensusInfo() override
{
return {};
}
json::Value
getServerInfo(bool, bool, bool) override
{
return {};
}
void
clearLedgerFetch() override
{
}
json::Value
getLedgerFetchInfo() override
{
return {};
}
std::uint32_t
acceptLedger(std::optional<std::chrono::milliseconds>) override
{
return {};
}
void
reportFeeChange() override
{
}
void
updateLocalTx(ReadView const&) override
{
}
std::size_t
getLocalTxCount() override
{
return {};
}
void
pubLedger(std::shared_ptr<ReadView const> const&) override
{
}
void
pubProposedTransaction(
std::shared_ptr<ReadView const> const&,
std::shared_ptr<STTx const> const&,
TER) override
{
}
void
pubValidation(std::shared_ptr<STValidation> const&) override
{
}
void
stateAccounting(json::Value&) override
{
}
std::size_t
getBookSubscribersCount() override
{
return {};
}
void
subAccount(InfoSub::Ref, HashSet<AccountID> const&, bool) override
{
}
void
unsubAccount(InfoSub::Ref, HashSet<AccountID> const&, bool) override
{
}
void
unsubAccountInternal(std::uint64_t, HashSet<AccountID> const&, bool) override
{
}
ErrorCodeI
subAccountHistory(InfoSub::Ref, AccountID const&) override
{
return {};
}
void
unsubAccountHistory(InfoSub::Ref, AccountID const&, bool) override
{
}
void
unsubAccountHistoryInternal(std::uint64_t, AccountID const&, bool) override
{
}
void
unsubMPTInternal(std::uint64_t, MPTID const&) override
{
}
void
scheduleAccountCleanup(
std::uint64_t,
HashSet<AccountID>,
HashSet<AccountID>,
HashSet<AccountID>) override
{
}
bool
subLedger(InfoSub::Ref, json::Value&) override
{
return {};
}
bool
unsubLedger(std::uint64_t) override
{
return {};
}
bool
subBookChanges(InfoSub::Ref) override
{
return {};
}
bool
unsubBookChanges(std::uint64_t) override
{
return {};
}
bool
subManifests(InfoSub::Ref) override
{
return {};
}
bool
unsubManifests(std::uint64_t) override
{
return {};
}
void
pubManifest(Manifest const&) override
{
}
bool
subServer(InfoSub::Ref, json::Value&, bool) override
{
return {};
}
bool
unsubServer(std::uint64_t) override
{
return {};
}
bool
subBook(InfoSub::Ref, Book const&) override
{
return {};
}
bool
unsubBook(InfoSub::Ref, Book const&) override
{
return {};
}
bool
unsubBookInternal(std::uint64_t, Book const&) override
{
return {};
}
bool
subTransactions(InfoSub::Ref) override
{
return {};
}
bool
unsubTransactions(std::uint64_t) override
{
return {};
}
bool
subRTTransactions(InfoSub::Ref) override
{
return {};
}
bool
unsubRTTransactions(std::uint64_t) override
{
return {};
}
bool
subValidations(InfoSub::Ref) override
{
return {};
}
bool
unsubValidations(std::uint64_t) override
{
return {};
}
bool
subPeerStatus(InfoSub::Ref) override
{
return {};
}
bool
unsubPeerStatus(std::uint64_t) override
{
return {};
}
void
pubPeerStatus(std::function<json::Value()> const&) override
{
}
bool
subConsensus(InfoSub::Ref) override
{
return {};
}
bool
unsubConsensus(std::uint64_t) override
{
return {};
}
void
subMPT(InfoSub::Ref, HashSet<MPTID> const&) override
{
}
void
unsubMPT(InfoSub::Ref, HashSet<MPTID> const&) override
{
}
InfoSub::pointer
findRpcSub(std::string const&) override
{
return {};
}
InfoSub::pointer
addRpcSub(std::string const&, InfoSub::Ref) override
{
return {};
}
bool
tryRemoveRpcSub(std::string const&) override
{
return {};
}
[[nodiscard]] beast::Journal const&
journal() const override
{
return j_;
}
private:
beast::Journal j_{beast::Journal::getNullSink()};
};
} // namespace xrpl::test
+31
View File
@@ -16,10 +16,12 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/JsonRpc.h>
#include <xrpl/protocol/PathAsset.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/SField.h>
@@ -363,6 +365,35 @@ getAccountLines(Env& env, AccountID const& acctId, IOU... ious)
[[nodiscard]] bool
checkArraySize(json::Value const& val, unsigned int size);
/**
* Reports whether @p reply carries the version 2 envelope: `jsonrpc` equal to
* rpc::kJsonRpcVersion and `ripplerpc` equal to rpc::kRippleRpcVersion2, both
* present.
*
* @param reply The reply to inspect.
* @return True when both members are present with those values.
*/
[[nodiscard]]
inline bool
hasEnvelope2(json::Value const& reply)
{
return reply.isMember(jss::jsonrpc) && reply[jss::jsonrpc] == rpc::kJsonRpcVersion &&
reply.isMember(jss::ripplerpc) && reply[jss::ripplerpc] == rpc::kRippleRpcVersion2;
}
/**
* Sets the version 2 envelope on @p request: `jsonrpc` to rpc::kJsonRpcVersion
* and `ripplerpc` to rpc::kRippleRpcVersion2.
*
* @param request The request to stamp.
*/
inline void
setEnvelope2(json::Value& request)
{
request[jss::jsonrpc] = rpc::kJsonRpcVersion;
request[jss::ripplerpc] = rpc::kRippleRpcVersion2;
}
// Helper function that returns the owner count on an account.
std::uint32_t
ownerCount(test::jtx::Env const& env, test::jtx::Account const& account);
+4 -2
View File
@@ -10,6 +10,8 @@
#include <xrpl/json/json_reader.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/JsonRpc.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/server/Port.h>
@@ -163,8 +165,8 @@ public:
jr[jss::method] = cmd;
if (rpcVersion_ == 2)
{
jr[jss::jsonrpc] = "2.0";
jr[jss::ripplerpc] = "2.0";
jr[jss::jsonrpc] = rpc::kJsonRpcVersion;
jr[jss::ripplerpc] = rpc::kRippleRpcVersion2;
jr[jss::id] = 5;
}
if (params)
+4 -2
View File
@@ -9,6 +9,8 @@
#include <xrpl/json/json_reader.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/JsonRpc.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/server/Port.h>
@@ -207,8 +209,8 @@ public:
if (rpcVersion_ == 2)
{
jp[jss::method] = cmd;
jp[jss::jsonrpc] = "2.0";
jp[jss::ripplerpc] = "2.0";
jp[jss::jsonrpc] = rpc::kJsonRpcVersion;
jp[jss::ripplerpc] = rpc::kRippleRpcVersion2;
jp[jss::id] = 5;
}
else
+8 -14
View File
@@ -1,6 +1,7 @@
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/amount.h>
#include <test/jtx/flags.h>
#include <test/jtx/multisign.h>
@@ -426,8 +427,7 @@ public:
BEAST_EXPECT(
info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
BEAST_EXPECT(!info[jss::result][jss::account_data].isMember(jss::signer_lists));
BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(info));
BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5);
}
{
@@ -440,8 +440,7 @@ public:
auto const& signerLists = data[jss::signer_lists];
BEAST_EXPECT(signerLists.isArray());
BEAST_EXPECT(signerLists.size() == 0);
BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(info));
BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 6);
}
{
@@ -451,8 +450,7 @@ public:
info[0u].isMember(jss::result) &&
info[0u][jss::result].isMember(jss::account_data));
BEAST_EXPECT(!info[0u][jss::result][jss::account_data].isMember(jss::signer_lists));
BEAST_EXPECT(info[0u].isMember(jss::jsonrpc) && info[0u][jss::jsonrpc] == "2.0");
BEAST_EXPECT(info[0u].isMember(jss::ripplerpc) && info[0u][jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(info[0u]));
BEAST_EXPECT(info[0u].isMember(jss::id) && info[0u][jss::id] == 5);
BEAST_EXPECT(
@@ -463,8 +461,7 @@ public:
auto const& signerLists = data[jss::signer_lists];
BEAST_EXPECT(signerLists.isArray());
BEAST_EXPECT(signerLists.size() == 0);
BEAST_EXPECT(info[1u].isMember(jss::jsonrpc) && info[1u][jss::jsonrpc] == "2.0");
BEAST_EXPECT(info[1u].isMember(jss::ripplerpc) && info[1u][jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(info[1u]));
BEAST_EXPECT(info[1u].isMember(jss::id) && info[1u][jss::id] == 6);
}
@@ -479,8 +476,7 @@ public:
BEAST_EXPECT(
info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
BEAST_EXPECT(!info[jss::result][jss::account_data].isMember(jss::signer_lists));
BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(info));
BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5);
}
{
@@ -500,8 +496,7 @@ public:
BEAST_EXPECT(signerEntries.size() == 1);
auto const& entry0 = signerEntries[0u][sfSignerEntry.jsonName];
BEAST_EXPECT(entry0[sfSignerWeight.jsonName] == 3);
BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(info));
BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 6);
}
@@ -550,8 +545,7 @@ public:
BEAST_EXPECT(entry.isMember(sfAccount.jsonName));
BEAST_EXPECT(entry[sfSignerWeight.jsonName] == 1);
}
BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(info));
BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 6);
}
}
+77 -129
View File
@@ -915,18 +915,15 @@ public:
// account_lines with mal-formed json2 (missing id field).
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
}
{
// account_lines with no account.
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
@@ -934,8 +931,7 @@ public:
BEAST_EXPECT(
lines[jss::error][jss::message] ==
rpc::missingFieldError(jss::account)[jss::error_message]);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -944,8 +940,7 @@ public:
params[jss::account] = "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -954,8 +949,7 @@ public:
BEAST_EXPECT(
lines[jss::error][jss::message] ==
rpc::makeError(RpcActMalformed)[jss::error_message]);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
Account const alice{"alice"};
@@ -965,8 +959,7 @@ public:
params[jss::account] = alice.human();
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -975,8 +968,7 @@ public:
BEAST_EXPECT(
lines[jss::error][jss::message] ==
rpc::makeError(RpcActNotFound)[jss::error_message]);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
env.fund(XRP(10000), alice);
@@ -993,8 +985,7 @@ public:
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
@@ -1002,8 +993,7 @@ public:
BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(lines[jss::error][jss::message] == "Invalid field 'peer'.");
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
};
@@ -1020,15 +1010,13 @@ public:
params[jss::account] = alice.human();
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1038,8 +1026,7 @@ public:
params[jss::ledger_index] = "nonsense";
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -1048,8 +1035,7 @@ public:
BEAST_EXPECT(
lines[jss::error][jss::message] ==
"Invalid field 'ledger_index', not string or number.");
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1059,16 +1045,14 @@ public:
params[jss::ledger_index] = 50000;
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "lgrNotFound");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(lines[jss::error][jss::message] == "ledgerNotFound");
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
// Create trust lines to share with alice.
@@ -1121,44 +1105,38 @@ public:
BEAST_EXPECT(ledger58Info.seq == 58);
// A re-usable test for historic ledgers.
auto const testAccountLinesHistory = [this, &env](
Account const& account,
LedgerHeader const& info,
int count) {
// Get account_lines by ledger index.
json::Value paramsSeq;
paramsSeq[jss::account] = account.human();
paramsSeq[jss::ledger_index] = info.seq;
json::Value requestSeq;
requestSeq[jss::method] = "account_lines";
requestSeq[jss::jsonrpc] = "2.0";
requestSeq[jss::ripplerpc] = "2.0";
requestSeq[jss::id] = 5;
requestSeq[jss::params] = paramsSeq;
auto const linesSeq = env.rpc("json2", to_string(requestSeq));
BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
BEAST_EXPECT(linesSeq.isMember(jss::jsonrpc) && linesSeq[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesSeq.isMember(jss::ripplerpc) && linesSeq[jss::ripplerpc] == "2.0");
BEAST_EXPECT(linesSeq.isMember(jss::id) && linesSeq[jss::id] == 5);
auto const testAccountLinesHistory =
[this, &env](Account const& account, LedgerHeader const& info, int count) {
// Get account_lines by ledger index.
json::Value paramsSeq;
paramsSeq[jss::account] = account.human();
paramsSeq[jss::ledger_index] = info.seq;
json::Value requestSeq;
requestSeq[jss::method] = "account_lines";
setEnvelope2(requestSeq);
requestSeq[jss::id] = 5;
requestSeq[jss::params] = paramsSeq;
auto const linesSeq = env.rpc("json2", to_string(requestSeq));
BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
BEAST_EXPECT(hasEnvelope2(linesSeq));
BEAST_EXPECT(linesSeq.isMember(jss::id) && linesSeq[jss::id] == 5);
// Get account_lines by ledger hash.
json::Value paramsHash;
paramsHash[jss::account] = account.human();
paramsHash[jss::ledger_hash] = to_string(info.hash);
json::Value requestHash;
requestHash[jss::method] = "account_lines";
requestHash[jss::jsonrpc] = "2.0";
requestHash[jss::ripplerpc] = "2.0";
requestHash[jss::id] = 5;
requestHash[jss::params] = paramsHash;
auto const linesHash = env.rpc("json2", to_string(requestHash));
BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
BEAST_EXPECT(linesHash.isMember(jss::jsonrpc) && linesHash[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesHash.isMember(jss::ripplerpc) && linesHash[jss::ripplerpc] == "2.0");
BEAST_EXPECT(linesHash.isMember(jss::id) && linesHash[jss::id] == 5);
};
// Get account_lines by ledger hash.
json::Value paramsHash;
paramsHash[jss::account] = account.human();
paramsHash[jss::ledger_hash] = to_string(info.hash);
json::Value requestHash;
requestHash[jss::method] = "account_lines";
setEnvelope2(requestHash);
requestHash[jss::id] = 5;
requestHash[jss::params] = paramsHash;
auto const linesHash = env.rpc("json2", to_string(requestHash));
BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
BEAST_EXPECT(hasEnvelope2(linesHash));
BEAST_EXPECT(linesHash.isMember(jss::id) && linesHash[jss::id] == 5);
};
// Alice should have no trust lines in ledger 3.
testAccountLinesHistory(alice, ledger3Info, 0);
@@ -1178,8 +1156,7 @@ public:
params[jss::ledger_index] = ledger58Info.seq;
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -1189,8 +1166,7 @@ public:
lines[jss::error][jss::message] ==
"Exactly one of 'ledger_hash' or 'ledger_index' can be "
"specified.");
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1199,15 +1175,13 @@ public:
params[jss::account] = alice.human();
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1217,15 +1191,13 @@ public:
params[jss::peer] = gw1.human();
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1235,8 +1207,7 @@ public:
params[jss::peer] = "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -1245,8 +1216,7 @@ public:
BEAST_EXPECT(
lines[jss::error][jss::message] ==
rpc::makeError(RpcActMalformed)[jss::error_message]);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1256,8 +1226,7 @@ public:
params[jss::limit] = -1;
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -1266,8 +1235,7 @@ public:
BEAST_EXPECT(
lines[jss::error][jss::message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1277,15 +1245,13 @@ public:
paramsA[jss::limit] = 1;
json::Value requestA;
requestA[jss::method] = "account_lines";
requestA[jss::jsonrpc] = "2.0";
requestA[jss::ripplerpc] = "2.0";
setEnvelope2(requestA);
requestA[jss::id] = 5;
requestA[jss::params] = paramsA;
auto const linesA = env.rpc("json2", to_string(requestA));
BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
BEAST_EXPECT(linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesA.isMember(jss::ripplerpc) && linesA[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesA));
BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
// Pick up from where the marker left off. We should get 51.
@@ -1295,15 +1261,13 @@ public:
paramsB[jss::marker] = marker;
json::Value requestB;
requestB[jss::method] = "account_lines";
requestB[jss::jsonrpc] = "2.0";
requestB[jss::ripplerpc] = "2.0";
setEnvelope2(requestB);
requestB[jss::id] = 5;
requestB[jss::params] = paramsB;
auto const linesB = env.rpc("json2", to_string(requestB));
BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
BEAST_EXPECT(linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesB.isMember(jss::ripplerpc) && linesB[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesB));
BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
// Go again from where the marker left off, but set a limit of 3.
@@ -1313,15 +1277,13 @@ public:
paramsC[jss::marker] = marker;
json::Value requestC;
requestC[jss::method] = "account_lines";
requestC[jss::jsonrpc] = "2.0";
requestC[jss::ripplerpc] = "2.0";
setEnvelope2(requestC);
requestC[jss::id] = 5;
requestC[jss::params] = paramsC;
auto const linesC = env.rpc("json2", to_string(requestC));
BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
BEAST_EXPECT(linesC.isMember(jss::jsonrpc) && linesC[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesC.isMember(jss::ripplerpc) && linesC[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesC));
BEAST_EXPECT(linesC.isMember(jss::id) && linesC[jss::id] == 5);
// Mess with the marker so it becomes bad and check for the error.
@@ -1331,8 +1293,7 @@ public:
paramsD[jss::marker] = marker;
json::Value requestD;
requestD[jss::method] = "account_lines";
requestD[jss::jsonrpc] = "2.0";
requestD[jss::ripplerpc] = "2.0";
setEnvelope2(requestD);
requestD[jss::id] = 5;
requestD[jss::params] = paramsD;
auto const linesD = env.rpc("json2", to_string(requestD));
@@ -1341,8 +1302,7 @@ public:
BEAST_EXPECT(
linesD[jss::error][jss::message] ==
rpc::makeError(RpcInvalidParams)[jss::error_message]);
BEAST_EXPECT(linesD.isMember(jss::jsonrpc) && linesD[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesD.isMember(jss::ripplerpc) && linesD[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesD));
BEAST_EXPECT(linesD.isMember(jss::id) && linesD[jss::id] == 5);
}
{
@@ -1352,8 +1312,7 @@ public:
params[jss::marker] = true;
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -1362,8 +1321,7 @@ public:
BEAST_EXPECT(
lines[jss::error][jss::message] ==
rpc::expectedFieldMessage(jss::marker, "string"));
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1374,8 +1332,7 @@ public:
params[jss::peer] = gw2.human();
json::Value request;
request[jss::method] = "account_lines";
request[jss::jsonrpc] = "2.0";
request[jss::ripplerpc] = "2.0";
setEnvelope2(request);
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
@@ -1384,8 +1341,7 @@ public:
BEAST_EXPECT(line[jss::deep_freeze].asBool() == true);
BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(lines));
BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
}
{
@@ -1396,8 +1352,7 @@ public:
paramsA[jss::peer] = alice.human();
json::Value requestA;
requestA[jss::method] = "account_lines";
requestA[jss::jsonrpc] = "2.0";
requestA[jss::ripplerpc] = "2.0";
setEnvelope2(requestA);
requestA[jss::id] = 5;
requestA[jss::params] = paramsA;
auto const linesA = env.rpc("json2", to_string(requestA));
@@ -1406,8 +1361,7 @@ public:
BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true);
BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
BEAST_EXPECT(linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesA.isMember(jss::ripplerpc) && linesA[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesA));
BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
// Continue from the returned marker to make sure that works.
@@ -1420,16 +1374,14 @@ public:
paramsB[jss::peer] = alice.human();
json::Value requestB;
requestB[jss::method] = "account_lines";
requestB[jss::jsonrpc] = "2.0";
requestB[jss::ripplerpc] = "2.0";
setEnvelope2(requestB);
requestB[jss::id] = 5;
requestB[jss::params] = paramsB;
auto const linesB = env.rpc("json2", to_string(requestB));
BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
BEAST_EXPECT(linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesB.isMember(jss::ripplerpc) && linesB[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesB));
BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
}
}
@@ -1489,15 +1441,13 @@ public:
linesBegParams[jss::limit] = 2;
json::Value linesBegRequest;
linesBegRequest[jss::method] = "account_lines";
linesBegRequest[jss::jsonrpc] = "2.0";
linesBegRequest[jss::ripplerpc] = "2.0";
setEnvelope2(linesBegRequest);
linesBegRequest[jss::id] = 5;
linesBegRequest[jss::params] = linesBegParams;
auto const linesBeg = env.rpc("json2", to_string(linesBegRequest));
BEAST_EXPECT(linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
BEAST_EXPECT(linesBeg.isMember(jss::jsonrpc) && linesBeg[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesBeg.isMember(jss::ripplerpc) && linesBeg[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesBeg));
BEAST_EXPECT(linesBeg.isMember(jss::id) && linesBeg[jss::id] == 5);
// alice pays 100 USD to cheri.
@@ -1511,8 +1461,7 @@ public:
linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
json::Value linesEndRequest;
linesEndRequest[jss::method] = "account_lines";
linesEndRequest[jss::jsonrpc] = "2.0";
linesEndRequest[jss::ripplerpc] = "2.0";
setEnvelope2(linesEndRequest);
linesEndRequest[jss::id] = 5;
linesEndRequest[jss::params] = linesEndParams;
auto const linesEnd = env.rpc("json2", to_string(linesEndRequest));
@@ -1521,8 +1470,7 @@ public:
BEAST_EXPECT(
linesEnd[jss::error][jss::message] ==
rpc::makeError(RpcInvalidParams)[jss::error_message]);
BEAST_EXPECT(linesEnd.isMember(jss::jsonrpc) && linesEnd[jss::jsonrpc] == "2.0");
BEAST_EXPECT(linesEnd.isMember(jss::ripplerpc) && linesEnd[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(linesEnd));
BEAST_EXPECT(linesEnd.isMember(jss::id) && linesEnd[jss::id] == 5);
}
+21 -40
View File
@@ -1,4 +1,5 @@
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/amount.h>
#include <test/jtx/domain.h>
@@ -92,8 +93,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -130,8 +130,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -169,8 +168,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -213,8 +211,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -246,8 +243,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -292,8 +288,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -332,8 +327,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -390,8 +384,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -432,8 +425,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -491,8 +483,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -546,8 +537,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -617,8 +607,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -661,8 +650,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -735,8 +723,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -793,8 +780,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -891,8 +877,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -925,8 +910,7 @@ public:
auto jv = wsc->invoke("subscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
if (!BEAST_EXPECT(jv[jss::status] == "success"))
@@ -956,8 +940,7 @@ public:
auto jv = wsc->invoke("book_offers", jvParams);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
auto jrr = jv[jss::result];
@@ -1001,8 +984,7 @@ public:
jv = wsc->invoke("book_offers", jvParams);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
jrr = jv[jss::result];
@@ -1027,8 +1009,7 @@ public:
jv = wsc->invoke("unsubscribe", books);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
+3 -2
View File
@@ -1,5 +1,6 @@
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/amount.h>
#include <test/jtx/balance.h>
@@ -22,6 +23,7 @@
#include <xrpl/protocol/jss.h>
#include <chrono>
#include <string>
#include <utility>
namespace xrpl::test {
@@ -254,8 +256,7 @@ class DeliveredAmount_test : public beast::unit_test::Suite
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 3);
+2 -2
View File
@@ -1,5 +1,6 @@
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/amount.h>
#include <test/jtx/escrow.h>
@@ -86,8 +87,7 @@ public:
expect(jv[jss::status] == "success");
if (wsc->version() == 2)
{
expect(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
expect(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
expect(hasEnvelope2(jv));
expect(jv.isMember(jss::id) && jv[jss::id] == 5);
}
+3 -10
View File
@@ -348,12 +348,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
if (apiVersion < 2u)
{
BEAST_EXPECTS(jrr[jss::status] == "error", std::to_string(location.line()));
BEAST_EXPECTS(
jrr[jss::error] == "unknownOption", std::to_string(location.line()));
BEAST_EXPECTS(!jrr.isMember(jss::error_code), std::to_string(location.line()));
BEAST_EXPECTS(
jrr[jss::error_message].isNull(), std::to_string(location.line()));
checkErrorValue(
jrr, "unknownOption", RpcUnknownOption, "Unknown option.", location);
}
else
{
@@ -575,10 +571,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
if (apiVersion < 2u)
{
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "unknownOption");
BEAST_EXPECT(!jrr.isMember(jss::error_code));
BEAST_EXPECT(jrr[jss::error_message].isNull());
checkErrorValue(jrr, "unknownOption", RpcUnknownOption, "Unknown option.");
}
else
{
+397
View File
@@ -0,0 +1,397 @@
#include <test/jtx/Env.h>
#include <xrpld/rpc/detail/MaskSecrets.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_reader.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <array>
#include <string>
#include <string_view>
namespace xrpl::test {
class MaskSecrets_test : public beast::unit_test::Suite
{
static constexpr char const* kMasked = "<masked>";
static constexpr char const* kSensitive = "sensitive";
public:
/**
* Every field in `kCredentialFields` is masked, at the top level.
*/
void
testEveryCredentialField()
{
testcase("Every credential field is masked");
json::Value request(json::ValueType::Object);
for (auto const field : rpc::kCredentialFields)
request[std::string{field}] = kSensitive;
auto const masked = rpc::maskSecrets(request);
for (auto const field : rpc::kCredentialFields)
BEAST_EXPECTS(masked[std::string{field}] == kMasked, std::string{field});
// An empty list would let every assertion above pass while masking nothing.
BEAST_EXPECT(!rpc::kCredentialFields.empty());
}
/**
* A field the list does not name is left as it was.
*/
void
testNonCredentialsSurvive()
{
testcase("Fields that are not credentials are left alone");
json::Value request(json::ValueType::Object);
request[jss::method] = "sign";
request[jss::account] = "rSomeAccount";
request[jss::secret] = kSensitive;
auto const masked = rpc::maskSecrets(request);
BEAST_EXPECT(masked[jss::secret] == kMasked);
BEAST_EXPECT(masked[jss::method] == "sign");
BEAST_EXPECT(masked[jss::account] == "rSomeAccount");
// Masking replaces members rather than adding or removing them.
BEAST_EXPECT(masked.size() == request.size());
}
/**
* A member name is matched whole, not as a substring or case-insensitively.
*/
void
testNamesAreMatchedExactly()
{
testcase("Only an exact field name is masked");
json::Value request(json::ValueType::Object);
for (auto const* name : {"secrets", "Secret", "my_secret", "seedling", "sec", ""})
request[name] = kSensitive;
auto const masked = rpc::maskSecrets(request);
for (auto const* name : {"secrets", "Secret", "my_secret", "seedling", "sec", ""})
BEAST_EXPECTS(masked[name] == kSensitive, name);
}
/**
* A credential is masked wherever it sits, not only at the top level.
*
* The JSON-RPC transport nests a request's fields inside `params`, which is
* where a `secret` arrives.
*/
void
testNesting()
{
testcase("A nested credential is masked at any depth");
// Where the JSON-RPC transport carries a credential: inside `params`.
{
json::Value params(json::ValueType::Object);
params[jss::secret] = kSensitive;
params[jss::account] = "rSomeAccount";
json::Value request(json::ValueType::Object);
request[jss::method] = "sign";
request[jss::params] = json::ValueType::Array;
request[jss::params][0u] = params;
auto const masked = rpc::maskSecrets(request);
BEAST_EXPECT(masked[jss::params][0u][jss::secret] == kMasked);
BEAST_EXPECT(masked[jss::params][0u][jss::account] == "rSomeAccount");
BEAST_EXPECT(masked[jss::method] == "sign");
}
// Where a batch carries one: inside an entry, itself inside `params`.
{
json::Value entry(json::ValueType::Object);
entry[jss::id] = 2;
entry[jss::seed] = kSensitive;
json::Value batch(json::ValueType::Object);
batch[jss::method] = "batch";
batch[jss::params] = json::ValueType::Array;
batch[jss::params][0u] = entry;
auto const masked = rpc::maskSecrets(batch);
BEAST_EXPECT(masked[jss::params][0u][jss::seed] == kMasked);
BEAST_EXPECT(masked[jss::params][0u][jss::id] == 2);
}
// A credential in an array element, which names no member of its own.
{
json::Value inner(json::ValueType::Object);
inner[jss::passphrase] = kSensitive;
json::Value request(json::ValueType::Array);
request[0u] = inner;
auto const masked = rpc::maskSecrets(request);
BEAST_EXPECT(masked[0u][jss::passphrase] == kMasked);
}
// Up to the parser's nesting limit, which is what bounds the recursion.
{
json::Value deep(json::ValueType::Object);
deep[jss::secret] = kSensitive;
for (unsigned i{0}; i < json::Reader::kNestLimit; ++i)
{
json::Value outer(json::ValueType::Object);
outer[jss::params] = deep;
deep = outer;
}
auto const masked = rpc::maskSecrets(deep);
auto const* level = &masked;
for (unsigned i{0}; i < json::Reader::kNestLimit; ++i)
level = &(*level)[jss::params];
BEAST_EXPECT((*level)[jss::secret] == kMasked);
}
}
/**
* A credential's own value is replaced whole, whatever shape it has.
*
* An object or array under `secret` is not walked into: the whole value is
* the credential.
*/
void
testCredentialIsNotDescendedInto()
{
testcase("A credential is replaced whatever it holds");
json::Value nested(json::ValueType::Object);
nested["inner"] = kSensitive;
json::Value request(json::ValueType::Object);
request[jss::secret] = nested;
request[jss::seed] = json::ValueType::Array;
request[jss::seed][0u] = kSensitive;
auto const masked = rpc::maskSecrets(request);
BEAST_EXPECT(masked[jss::secret] == kMasked);
BEAST_EXPECT(masked[jss::seed] == kMasked);
BEAST_EXPECT(!to_string(masked).contains(kSensitive));
}
/**
* A credential that is not a string is masked too, the value being replaced
* whole.
*/
void
testNonStringCredentials()
{
testcase("A credential of any type is masked");
json::Value request(json::ValueType::Object);
request["secret"] = 12345;
request["seed"] = true;
request["passphrase"] = 1.5;
request["password"] = json::ValueType::Null;
auto const masked = rpc::maskSecrets(request);
for (auto const* field : {"secret", "seed", "passphrase", "password"})
BEAST_EXPECTS(masked[field] == kMasked, field);
}
/**
* A value with no members is returned unchanged, masking keying on member
* names.
*/
void
testValuesWithoutMembers()
{
testcase("A value that has no members is returned unchanged");
BEAST_EXPECT(rpc::maskSecrets(json::Value{}).isNull());
BEAST_EXPECT(rpc::maskSecrets(json::Value{kSensitive}) == kSensitive);
BEAST_EXPECT(rpc::maskSecrets(json::Value{42}) == 42);
BEAST_EXPECT(rpc::maskSecrets(json::Value{json::ValueType::Object}).size() == 0);
BEAST_EXPECT(rpc::maskSecrets(json::Value{json::ValueType::Array}).size() == 0);
}
/**
* The request the caller passed in is not modified.
*
* Every call site goes on to use the request it holds, so the mask copies.
*/
void
testRequestIsNotModified()
{
testcase("The request passed in is left unchanged");
json::Value request(json::ValueType::Object);
request[jss::secret] = kSensitive;
request[jss::params] = json::ValueType::Array;
request[jss::params][0u] = json::ValueType::Object;
request[jss::params][0u][jss::seed] = kSensitive;
auto const before = to_string(request);
auto const masked = rpc::maskSecrets(request);
BEAST_EXPECT(to_string(request) == before);
BEAST_EXPECT(request[jss::secret] == kSensitive);
BEAST_EXPECT(request[jss::params][0u][jss::seed] == kSensitive);
BEAST_EXPECT(masked[jss::secret] == kMasked);
}
/**
* `hasSecret` reports a credential at any depth and nothing else.
*
* Every name on the list is reported at the top level, one nested where the
* JSON-RPC transport puts it and one inside an array element are reported
* too, and a value naming none, or having no members at all, is not.
*/
void
testHasSecret()
{
testcase("A credential is detected at any depth");
for (auto const field : rpc::kCredentialFields)
{
json::Value request(json::ValueType::Object);
request[std::string{field}] = kSensitive;
BEAST_EXPECTS(rpc::hasSecret(request), std::string{field});
}
// Inside `params`, where the JSON-RPC transport carries a credential.
{
json::Value request(json::ValueType::Object);
request[jss::method] = "sign";
request[jss::params] = json::ValueType::Array;
request[jss::params][0u] = json::ValueType::Object;
request[jss::params][0u][jss::account] = "rSomeAccount";
request[jss::params][0u][jss::secret] = kSensitive;
BEAST_EXPECT(rpc::hasSecret(request));
}
// Inside an array element, which names no member of its own.
{
json::Value request(json::ValueType::Array);
request[0u] = json::ValueType::Object;
request[0u][jss::passphrase] = kSensitive;
BEAST_EXPECT(rpc::hasSecret(request));
}
// No credential at any depth, and a near miss is not one.
{
json::Value request(json::ValueType::Object);
request[jss::method] = "account_info";
request[jss::params] = json::ValueType::Array;
request[jss::params][0u] = json::ValueType::Object;
request[jss::params][0u][jss::account] = "rSomeAccount";
request[jss::params][0u]["secrets"] = kSensitive;
BEAST_EXPECT(!rpc::hasSecret(request));
}
// A value with no members carries none.
BEAST_EXPECT(!rpc::hasSecret(json::Value{}));
BEAST_EXPECT(!rpc::hasSecret(json::Value{kSensitive}));
BEAST_EXPECT(!rpc::hasSecret(json::Value{42}));
BEAST_EXPECT(!rpc::hasSecret(json::Value{json::ValueType::Object}));
BEAST_EXPECT(!rpc::hasSecret(json::Value{json::ValueType::Array}));
}
/**
* `loggable` masks a value that carries a credential, then truncates at
* `kMaxLoggedChars`; one that carries none is rendered as it is.
*
* The mask has to survive the cap, so the credential is placed where a
* sorted member order puts it first.
*/
void
testLoggable()
{
testcase("A rendering for a log line is masked and capped");
// Sorts before the filler, so the mask falls inside the cap.
json::Value request;
request[jss::secret] = kSensitive;
request["zfiller"] = std::string(2 * rpc::kMaxLoggedChars, 'z');
auto const rendered = rpc::loggable(request);
BEAST_EXPECT(rendered.size() == rpc::kMaxLoggedChars);
BEAST_EXPECT(rendered.contains(kMasked));
BEAST_EXPECT(!rendered.contains(kSensitive));
// A short request is not padded.
json::Value small;
small[jss::seed] = kSensitive;
BEAST_EXPECT(rpc::loggable(small) == to_string(rpc::maskSecrets(small)));
// A value with no credential is the serialized value itself, cut at the cap.
json::Value plain;
plain[jss::account] = "rSomeAccount";
plain["zfiller"] = std::string(2 * rpc::kMaxLoggedChars, 'z');
BEAST_EXPECT(rpc::loggable(plain) == to_string(plain).substr(0, rpc::kMaxLoggedChars));
BEAST_EXPECT(rpc::loggable(small[jss::seed]) == to_string(small[jss::seed]));
}
/**
* A keygen reply is rendered for the log with no key in it.
*
* Every field of each reply is checked, not only the names on the list, so
* a field the list is missing fails here. Only the fields a client may read
* in public are allowed through.
*/
void
testKeygenRepliesAreMasked()
{
testcase("A keygen reply is logged with every key masked");
static constexpr std::array<std::string_view, 6> kPublicFields{
"account_id",
"key_type",
"public_key",
"public_key_hex",
"status",
"validation_public_key",
};
jtx::Env env{*this};
for (auto const command : {"wallet_propose", "validation_create"})
{
auto const reply = env.rpc(command);
auto const& result = reply[jss::result];
BEAST_EXPECT(result[jss::status] == "success");
auto const rendered = rpc::loggable(reply);
for (auto const& name : result.getMemberNames())
{
if (std::ranges::find(kPublicFields, name) != kPublicFields.end())
continue;
auto const& value = result[name];
BEAST_EXPECT(value.isString() && !value.asString().empty());
BEAST_EXPECTS(!rendered.contains(value.asString()), command + (": " + name));
}
}
}
void
run() override
{
testEveryCredentialField();
testNonCredentialsSurvive();
testNamesAreMatchedExactly();
testNesting();
testCredentialIsNotDescendedInto();
testNonStringCredentials();
testValuesWithoutMembers();
testRequestIsNotModified();
testHasSecret();
testLoggable();
testKeygenRepliesAreMasked();
}
};
BEAST_DEFINE_TESTSUITE(MaskSecrets, rpc, xrpl);
} // namespace xrpl::test
+22 -42
View File
@@ -1,4 +1,5 @@
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/amount.h>
#include <test/jtx/balance.h> // IWYU pragma: keep
@@ -38,8 +39,7 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
}
@@ -53,8 +53,7 @@ public:
auto jv = wsc->invoke("submit", payment);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tefMAX_LEDGER");
@@ -65,8 +64,7 @@ public:
jv = wsc->invoke("submit", payment);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tefPAST_SEQ");
@@ -76,8 +74,7 @@ public:
jv = wsc->invoke("submit", payment);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::engine_result] == "terPRE_SEQ");
@@ -87,8 +84,7 @@ public:
jv = wsc->invoke("submit", payment);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
@@ -100,8 +96,7 @@ public:
jv = wsc->invoke("ledger_accept");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
@@ -128,8 +123,7 @@ public:
jv = wsc->invoke("unsubscribe", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -164,8 +158,7 @@ public:
jv = wsc->invoke("submit", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
@@ -187,8 +180,7 @@ public:
jv = wsc->invoke("account_tx", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
@@ -218,8 +210,7 @@ public:
jv = wsc->invoke("submit", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
@@ -228,8 +219,7 @@ public:
jv = wsc->invoke("ledger_accept");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
@@ -247,8 +237,7 @@ public:
jv = wsc->invoke("subscribe", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -260,8 +249,7 @@ public:
auto jv = wsc->invoke("ledger_accept");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
@@ -281,8 +269,7 @@ public:
jv = wsc->invoke("unsubscribe", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -300,8 +287,7 @@ public:
jv = wsc->invoke("subscribe", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -313,8 +299,7 @@ public:
auto jv = wsc->invoke("ledger_accept");
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
@@ -334,8 +319,7 @@ public:
jv = wsc->invoke("unsubscribe", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -352,8 +336,7 @@ public:
jv = wsc->invoke("account_tx", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
@@ -383,8 +366,7 @@ public:
jv = wsc->invoke("subscribe", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -399,8 +381,7 @@ public:
jv = wsc->invoke("submit", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
@@ -421,8 +402,7 @@ public:
jv = wsc->invoke("unsubscribe", jv);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
+17 -2
View File
@@ -338,8 +338,8 @@ class Simulate_test : public beast::unit_test::Suite
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!resp[jss::result].isMember(jss::error_code));
BEAST_EXPECT(resp[jss::result][jss::error_message].isNull());
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidTransaction);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Transaction is invalid.");
BEAST_EXPECT(
resp[jss::result][jss::error_exception] ==
"Field 'Destination' is required but missing.");
@@ -359,6 +359,21 @@ class Simulate_test : public beast::unit_test::Suite
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcSrcActMalformed);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Account'.");
}
{
// A non-string `Account`, which no earlier check rejects: the `tx_json` arm validates
// only that it is an object, and the check above only that `Account` is present.
json::Value params;
json::Value txJson = json::ValueType::Object;
txJson[jss::TransactionType] = jss::AccountSet;
txJson[jss::Account] = 123;
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECTS(
resp[jss::result][jss::error] == "invalidParams",
resp[jss::result][jss::error].toStyledString());
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Account'.");
}
{
// Account doesn't exist for Sequence autofill
json::Value params;
+15 -28
View File
@@ -1,4 +1,5 @@
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/amount.h>
#include <test/jtx/batch.h>
@@ -85,8 +86,7 @@ public:
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -115,8 +115,7 @@ public:
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -151,8 +150,7 @@ public:
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2);
@@ -186,8 +184,7 @@ public:
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -210,8 +207,7 @@ public:
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -268,8 +264,7 @@ public:
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -283,8 +278,7 @@ public:
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -316,8 +310,7 @@ public:
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -346,8 +339,7 @@ public:
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -392,8 +384,7 @@ public:
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -415,8 +406,7 @@ public:
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -426,8 +416,7 @@ public:
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -474,8 +463,7 @@ public:
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
@@ -572,8 +560,7 @@ public:
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(hasEnvelope2(jv));
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
+61 -24
View File
@@ -18,6 +18,7 @@
#include <xrpl/protocol/jss.h>
#include <memory>
#include <source_location>
#include <stdexcept>
#include <string>
@@ -25,6 +26,45 @@ namespace xrpl {
class TransactionEntry_test : public beast::unit_test::Suite
{
/**
* Asserts the reply carries the whole error, not just the token.
*
* @param result The reply's payload.
* @param token The `error` token expected.
* @param code The `error_code` expected.
* @param message The `error_message` expected.
* @param location The caller, reported when an assertion fails.
*/
void
checkCodedError(
json::Value const& result,
std::string const& token,
ErrorCodeI code,
std::string const& message,
std::source_location const location = std::source_location::current())
{
auto const at = std::to_string(location.line());
BEAST_EXPECTS(result[jss::error] == token, at);
BEAST_EXPECTS(result[jss::error_code] == code, at);
BEAST_EXPECTS(result[jss::error_message] == message, at);
BEAST_EXPECTS(result[jss::status] == "error", at);
}
/**
* Wire values pinned as literals, so reassigning a code fails here.
*/
void
testErrorCodeValues()
{
testcase("Error code values are stable");
BEAST_EXPECT(static_cast<int>(RpcFieldNotFoundTransaction) == 104);
BEAST_EXPECT(static_cast<int>(RpcNotYetImplemented) == 105);
BEAST_EXPECT(static_cast<int>(RpcTransactionNotFound) == 106);
BEAST_EXPECT(static_cast<int>(RpcMalformedRequest) == 107);
}
void
testBadInput()
{
@@ -38,10 +78,12 @@ class TransactionEntry_test : public beast::unit_test::Suite
{
// no params
auto const result = env.client().invoke("transaction_entry", {})[jss::result];
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "fieldNotFoundTransaction");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
// The message names the missing field rather than the table's default.
checkCodedError(
result,
"fieldNotFoundTransaction",
RpcFieldNotFoundTransaction,
"Missing field 'tx_hash'.");
}
{
@@ -59,10 +101,8 @@ class TransactionEntry_test : public beast::unit_test::Suite
params[jss::ledger] = "current";
params[jss::tx_hash] = "DEADBEEF";
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "notYetImplemented");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
checkCodedError(
result, "notYetImplemented", RpcNotYetImplemented, "Not yet implemented.");
}
{
@@ -71,10 +111,8 @@ class TransactionEntry_test : public beast::unit_test::Suite
params[jss::tx_hash] =
"E2FE8D4AF3FCC3944DDF6CD8CDDC5E3F0AD50863EF8919AFEF10CB6408CD4D05";
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "notYetImplemented");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
checkCodedError(
result, "notYetImplemented", RpcNotYetImplemented, "Not yet implemented.");
BEAST_EXPECT(result.isMember(jss::ledger_current_index));
BEAST_EXPECT(!result.isMember(jss::ledger_hash));
BEAST_EXPECT(result[jss::validated] == false);
@@ -86,22 +124,19 @@ class TransactionEntry_test : public beast::unit_test::Suite
params[jss::tx_hash] = "DEADBEEF";
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(!result[jss::ledger_hash].asString().empty());
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "malformedRequest");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
checkCodedError(
result, "malformedRequest", RpcMalformedRequest, "Request is malformed.");
}
// A `tx_hash` that is not a string is malformed, the same as one that is not hex.
for (auto const type : {json::ValueType::Object, json::ValueType::Array})
{
json::Value params{json::ValueType::Object};
params[jss::ledger] = "closed";
params[jss::tx_hash] = json::Value{type};
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "malformedRequest");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
checkCodedError(
result, "malformedRequest", RpcMalformedRequest, "Request is malformed.");
}
std::string const txHash{
@@ -176,10 +211,11 @@ class TransactionEntry_test : public beast::unit_test::Suite
// Valid structure, but transaction not found.
json::Value const result{env.rpc("transaction_entry", txHash, "closed")};
BEAST_EXPECT(!result[jss::result][jss::ledger_hash].asString().empty());
BEAST_EXPECT(result[jss::result][jss::status] == "error");
BEAST_EXPECT(result[jss::result][jss::error] == "transactionNotFound");
BEAST_EXPECT(!result[jss::result].isMember(jss::error_code));
BEAST_EXPECT(result[jss::result][jss::error_message].isNull());
checkCodedError(
result[jss::result],
"transactionNotFound",
RpcTransactionNotFound,
"Transaction not found.");
}
}
@@ -407,6 +443,7 @@ public:
void
run() override
{
testErrorCodeValues();
testBadInput();
forAllApiVersions([this](unsigned apiVersion) { testRequest(apiVersion); });
}
File diff suppressed because it is too large. Load diff
-17
View File
@@ -3,20 +3,3 @@
This directory contains unit tests for the project. The difference from existing `src/test` folder
is that we switch to 3rd party testing framework (`gtest`). We intend to gradually move existing tests
from our own framework to `gtest` and such tests will be moved to this new folder.
## Layout
`libxrpl/` has one directory per libxrpl module, and two that are not modules: `helpers/` for test
infrastructure shared by all of them, and `csf/` for the consensus simulation framework. Each
directory is compiled into its own object library, and all of them are linked into the single
`xrpl_tests` binary. `xrpld/` has one directory per server subsystem, all compiled into the
`xrpld_tests` binary.
A test includes a header from the test tree under its canonical path, `<tests/libxrpl/<dir>/...>` or
`<tests/xrpld/<dir>/...>`, the same way libxrpl's private headers are included. Under `libxrpl/` the
headers of a directory are only visible to that directory and to the directories that link its
target, so a test that needs another directory's headers has to declare that dependency in
`libxrpl/CMakeLists.txt`. The `helpers/` directory is the exception: every directory can include it
without declaring anything. Under `xrpld/` the directories cannot be isolated the same way, because
the server headers are not a library and the whole `src` tree is on the include path, so there the
canonical path is a convention only.
+51 -67
View File
@@ -5,74 +5,23 @@ include(verify_headers)
# Test requirements.
find_package(GTest REQUIRED)
# A single gtest binary, xrpl_tests, built from one object library per source
# directory below. Each directory's headers are reachable only through its own
# target, as <tests/libxrpl/<dir>/...>, so a test that needs another directory's
# headers has to link that directory's target: the dependencies between test
# directories are declared in this file and nowhere else, the same way the
# libxrpl modules declare theirs. The shared `helpers` directory is available
# to every other directory without declaring it.
#
# The object libraries link their dependencies only to compile against them.
# Their object files go into the binary as sources, so the binary links each
# library exactly once; linking the object libraries themselves would repeat
# every dependency on the link line, which the Apple linker rejects.
add_executable(xrpl_tests main.cpp)
# Single combined gtest binary built from the shared test helpers and all test
# modules below.
add_executable(
xrpl_tests
main.cpp
helpers/Account.cpp
helpers/TestSink.cpp
helpers/TxTest.cpp
)
patch_nix_binary(xrpl_tests)
set_target_properties(
xrpl_tests
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
)
# Lets test sources include the shared helpers as <helpers/...>.
target_include_directories(xrpl_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(xrpl_tests PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl)
if(rust)
target_link_libraries(xrpl_tests PRIVATE rs_hello_world_cxxbridge)
endif()
# Compiles ${CMAKE_CURRENT_SOURCE_DIR}/${name} into the object library
# xrpl_tests.${name}, exposes its headers under their canonical path, verifies
# them against that target, and adds the objects to xrpl_tests.
function(xrpl_add_test_dir name)
set(target xrpl_tests.${name})
file(
GLOB_RECURSE sources
CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
)
if(NOT rust)
# Tests of the Rust interop include generated cxxbridge headers, which
# do not exist without the crates, so keep them out of the build tree
# entirely. They are named `Rust<something>.cpp`.
list(FILTER sources EXCLUDE REGEX "/Rust[^/]*\\.cpp$")
endif()
add_library(${target} OBJECT ${sources})
target_link_libraries(
${target}
PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl
)
if(NOT name STREQUAL "helpers")
target_link_libraries(${target} PRIVATE xrpl_tests.helpers)
endif()
isolate_headers(
${target}
"${CMAKE_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
PUBLIC
)
# The test headers are not built with add_module, so verify them against
# their own target's compile environment.
if(verify_headers)
verify_target_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/${name}")
endif()
target_sources(xrpl_tests PRIVATE $<TARGET_OBJECTS:${target}>)
endfunction()
# The shared helpers first, since every other directory links them.
xrpl_add_test_dir(helpers)
# The CSF (Consensus Simulation Framework), used by the consensus tests. The
# consensus engine itself lives in libxrpl, so no xrpld sources or include paths
# are needed here.
xrpl_add_test_dir(csf)
# One source subdirectory per module. Network unit tests are currently not
# supported on Windows.
@@ -95,14 +44,49 @@ set(test_modules
if(NOT WIN32)
list(APPEND test_modules net)
endif()
if(rust)
target_link_libraries(xrpl_tests PRIVATE rs_hello_world_cxxbridge)
endif()
foreach(module IN LISTS test_modules)
xrpl_add_test_dir(${module})
# Append the module's sources (${module}/*.cpp and ${module}.cpp, if any).
file(
GLOB_RECURSE sources
CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/${module}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/${module}.cpp"
)
if(NOT rust)
# Tests of the Rust interop include generated cxxbridge headers, which
# do not exist without the crates, so keep them out of the build tree
# entirely. They are named `Rust<something>.cpp`.
list(FILTER sources EXCLUDE REGEX "/Rust[^/]*\\.cpp$")
endif()
target_sources(xrpl_tests PRIVATE ${sources})
# Expose the module's private headers under their canonical include path.
isolate_headers(
xrpl_tests
"${CMAKE_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}/tests/${module}"
PRIVATE
)
endforeach()
# Dependencies between test directories, beyond the shared helpers.
target_link_libraries(xrpl_tests.consensus PRIVATE xrpl_tests.csf)
if(rust)
target_link_libraries(xrpl_tests.basics PRIVATE rs_hello_world_cxxbridge)
# The consensus tests use the CSF (Consensus Simulation Framework) helpers, so
# compile the CSF sources into the test binary. The consensus engine itself now
# lives in libxrpl, so no xrpld sources or include paths are needed here.
file(
GLOB_RECURSE csf_sources
CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/csf/*.cpp"
)
target_sources(xrpl_tests PRIVATE ${csf_sources})
# The test helpers and per-module test headers are not built with add_module,
# so verify them against the test binary's own compile environment.
if(verify_headers)
verify_target_headers(xrpl_tests "${CMAKE_CURRENT_SOURCE_DIR}")
endif()
gtest_discover_tests(xrpl_tests DISCOVERY_TIMEOUT 60)
+1 -2
View File
@@ -1,5 +1,3 @@
#include <tests/libxrpl/helpers/TestSink.h>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/basics/TaggedCache.ipp> // IWYU pragma: keep
#include <xrpl/basics/chrono.h>
@@ -7,6 +5,7 @@
#include <xrpl/protocol/Protocol.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <string>
+1 -2
View File
@@ -1,7 +1,5 @@
#include <xrpl/basics/TaggedCache.h>
#include <tests/libxrpl/helpers/TestSink.h>
#include <xrpl/basics/IntrusivePointer.h>
#include <xrpl/basics/IntrusiveRefCounts.h>
#include <xrpl/basics/TaggedCache.ipp> // IWYU pragma: keep
@@ -10,6 +8,7 @@
#include <xrpl/protocol/Protocol.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <chrono>
#include <memory>
@@ -1,12 +1,11 @@
#include <tests/libxrpl/csf/Peer.h>
#include <tests/libxrpl/csf/PeerGroup.h>
#include <tests/libxrpl/csf/Sim.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/TrustGraph.h>
#include <tests/libxrpl/csf/collectors.h>
#include <xrpl/consensus/ConsensusParms.h>
#include <csf/Peer.h>
#include <csf/PeerGroup.h>
#include <csf/Sim.h>
#include <csf/SimTime.h>
#include <csf/TrustGraph.h>
#include <csf/collectors.h>
#include <gtest/gtest.h>
#include <chrono>
+11 -12
View File
@@ -1,17 +1,5 @@
#include <xrpl/consensus/Consensus.h>
#include <tests/libxrpl/csf/Peer.h>
#include <tests/libxrpl/csf/PeerGroup.h>
#include <tests/libxrpl/csf/Sim.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/Tx.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/collectors.h>
#include <tests/libxrpl/csf/events.h>
#include <tests/libxrpl/csf/random.h>
#include <tests/libxrpl/csf/submitters.h>
#include <tests/libxrpl/helpers/TestSink.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
@@ -20,7 +8,18 @@
#include <xrpl/consensus/DisputedTx.h>
#include <xrpl/ledger/LedgerTiming.h>
#include <csf/Peer.h>
#include <csf/PeerGroup.h>
#include <csf/Sim.h>
#include <csf/SimTime.h>
#include <csf/Tx.h>
#include <csf/Validation.h>
#include <csf/collectors.h>
#include <csf/events.h>
#include <csf/random.h>
#include <csf/submitters.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <chrono>
#include <cstddef>
@@ -1,10 +1,9 @@
#include <tests/libxrpl/csf/PeerGroup.h>
#include <tests/libxrpl/csf/Sim.h>
#include <tests/libxrpl/csf/collectors.h>
#include <tests/libxrpl/csf/random.h>
#include <tests/libxrpl/csf/submitters.h>
#include <tests/libxrpl/csf/timers.h>
#include <csf/PeerGroup.h>
#include <csf/Sim.h>
#include <csf/collectors.h>
#include <csf/random.h>
#include <csf/submitters.h>
#include <csf/timers.h>
#include <gtest/gtest.h>
#include <algorithm>
+1 -2
View File
@@ -1,7 +1,6 @@
#include <xrpl/consensus/LedgerTrie.h>
#include <tests/libxrpl/csf/ledgers.h>
#include <csf/ledgers.h>
#include <gtest/gtest.h>
#include <cstdint>
+6 -7
View File
@@ -1,12 +1,11 @@
#include <tests/libxrpl/csf/PeerGroup.h>
#include <tests/libxrpl/csf/Sim.h>
#include <tests/libxrpl/csf/collectors.h>
#include <tests/libxrpl/csf/random.h>
#include <tests/libxrpl/csf/submitters.h>
#include <tests/libxrpl/csf/timers.h>
#include <xrpl/consensus/ConsensusParms.h>
#include <csf/PeerGroup.h>
#include <csf/Sim.h>
#include <csf/collectors.h>
#include <csf/random.h>
#include <csf/submitters.h>
#include <csf/timers.h>
#include <gtest/gtest.h>
#include <chrono>
+3 -4
View File
@@ -1,9 +1,5 @@
#include <xrpl/consensus/Validations.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/ledgers.h>
#include <tests/libxrpl/helpers/TestSink.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/tagged_integer.h>
@@ -11,7 +7,10 @@
#include <xrpl/beast/clock/manual_clock.h>
#include <xrpl/beast/utility/Journal.h>
#include <csf/Validation.h>
#include <csf/ledgers.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <chrono>
#include <cstddef>
+2 -3
View File
@@ -1,7 +1,6 @@
#include <tests/libxrpl/csf/BasicNetwork.h>
#include <tests/libxrpl/csf/Scheduler.h>
#include <csf/BasicNetwork.h>
#include <csf/Scheduler.h>
#include <gtest/gtest.h>
#include <set>
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#include <tests/libxrpl/csf/Digraph.h>
#include <tests/libxrpl/csf/Scheduler.h>
#include <csf/Digraph.h>
#include <csf/Scheduler.h>
#include <cassert>
+6 -6
View File
@@ -1,11 +1,11 @@
#pragma once
#include <tests/libxrpl/csf/Proposal.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/Tx.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/events.h>
#include <tests/libxrpl/csf/ledgers.h>
#include <csf/Proposal.h>
#include <csf/SimTime.h>
#include <csf/Tx.h>
#include <csf/Validation.h>
#include <csf/events.h>
#include <csf/ledgers.h>
#include <memory>
#include <vector>
+1 -1
View File
@@ -1,4 +1,4 @@
#include <tests/libxrpl/csf/Digraph.h>
#include <csf/Digraph.h>
#include <gtest/gtest.h>
+1 -1
View File
@@ -1,4 +1,4 @@
#include <tests/libxrpl/csf/Histogram.h>
#include <csf/Histogram.h>
#include <gtest/gtest.h>
+11 -11
View File
@@ -1,16 +1,5 @@
#pragma once
#include <tests/libxrpl/csf/BasicNetwork.h>
#include <tests/libxrpl/csf/CollectorRef.h>
#include <tests/libxrpl/csf/Proposal.h>
#include <tests/libxrpl/csf/Scheduler.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/TrustGraph.h>
#include <tests/libxrpl/csf/Tx.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/events.h>
#include <tests/libxrpl/csf/ledgers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/chrono.h>
@@ -27,6 +16,17 @@
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <csf/BasicNetwork.h>
#include <csf/CollectorRef.h>
#include <csf/Proposal.h>
#include <csf/Scheduler.h>
#include <csf/SimTime.h>
#include <csf/TrustGraph.h>
#include <csf/Tx.h>
#include <csf/Validation.h>
#include <csf/events.h>
#include <csf/ledgers.h>
#include <algorithm>
#include <chrono>
#include <cmath>
+4 -4
View File
@@ -1,9 +1,9 @@
#pragma once
#include <tests/libxrpl/csf/Peer.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/random.h>
#include <csf/Peer.h>
#include <csf/SimTime.h>
#include <csf/Validation.h>
#include <csf/random.h>
#include <algorithm>
#include <cassert>
+4 -4
View File
@@ -1,11 +1,11 @@
#pragma once
#include <tests/libxrpl/csf/Tx.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/ledgers.h>
#include <xrpl/consensus/ConsensusProposal.h>
#include <csf/Tx.h>
#include <csf/Validation.h>
#include <csf/ledgers.h>
namespace xrpl::test::csf {
/**
* Proposal is a position taken in the consensus process and is represented
+1 -1
View File
@@ -1,4 +1,4 @@
#include <tests/libxrpl/csf/Scheduler.h>
#include <csf/Scheduler.h>
#include <gtest/gtest.h>
+9 -9
View File
@@ -1,16 +1,16 @@
#pragma once
#include <tests/libxrpl/csf/BasicNetwork.h>
#include <tests/libxrpl/csf/CollectorRef.h>
#include <tests/libxrpl/csf/Peer.h>
#include <tests/libxrpl/csf/PeerGroup.h>
#include <tests/libxrpl/csf/Scheduler.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/TrustGraph.h>
#include <tests/libxrpl/csf/ledgers.h>
#include <xrpl/beast/utility/Journal.h>
#include <csf/BasicNetwork.h>
#include <csf/CollectorRef.h>
#include <csf/Peer.h>
#include <csf/PeerGroup.h>
#include <csf/Scheduler.h>
#include <csf/SimTime.h>
#include <csf/TrustGraph.h>
#include <csf/ledgers.h>
#include <cstddef>
#include <cstdint>
#include <deque>
+2 -2
View File
@@ -1,9 +1,9 @@
#pragma once
#include <tests/libxrpl/csf/Digraph.h>
#include <boost/container/flat_set.hpp>
#include <csf/Digraph.h>
#include <algorithm>
#include <set>
#include <vector>
+2 -2
View File
@@ -1,10 +1,10 @@
#pragma once
#include <tests/libxrpl/csf/ledgers.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/tagged_integer.h>
#include <csf/ledgers.h>
#include <cstdint>
#include <optional>
#include <tuple>
+6 -6
View File
@@ -1,13 +1,13 @@
#pragma once
#include <tests/libxrpl/csf/Histogram.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/Tx.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/events.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <csf/Histogram.h>
#include <csf/SimTime.h>
#include <csf/Tx.h>
#include <csf/Validation.h>
#include <csf/events.h>
#include <algorithm>
#include <cassert>
#include <chrono>
+3 -3
View File
@@ -1,8 +1,8 @@
#pragma once
#include <tests/libxrpl/csf/Tx.h>
#include <tests/libxrpl/csf/Validation.h>
#include <tests/libxrpl/csf/ledgers.h>
#include <csf/Tx.h>
#include <csf/Validation.h>
#include <csf/ledgers.h>
namespace xrpl::test::csf {
+3 -3
View File
@@ -1,7 +1,7 @@
#include <tests/libxrpl/csf/Sim.h>
#include <csf/Sim.h>
#include <tests/libxrpl/csf/PeerGroup.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <csf/PeerGroup.h>
#include <csf/SimTime.h>
#include <algorithm>
#include <cstddef>
+3 -3
View File
@@ -1,11 +1,11 @@
#include <tests/libxrpl/csf/ledgers.h>
#include <tests/libxrpl/csf/Tx.h>
#include <csf/ledgers.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/LedgerTiming.h>
#include <csf/Tx.h>
#include <algorithm>
#include <chrono>
#include <cstddef>
+2 -2
View File
@@ -1,7 +1,5 @@
#pragma once
#include <tests/libxrpl/csf/Tx.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/tagged_integer.h>
#include <xrpl/json/json_value.h>
@@ -9,6 +7,8 @@
#include <boost/bimap/bimap.hpp>
#include <csf/Tx.h>
#include <cassert>
#include <cstddef>
#include <cstdint>
+3 -3
View File
@@ -1,8 +1,8 @@
#pragma once
#include <tests/libxrpl/csf/Scheduler.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <tests/libxrpl/csf/Tx.h>
#include <csf/Scheduler.h>
#include <csf/SimTime.h>
#include <csf/Tx.h>
#include <cstddef>
#include <cstdint>
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#include <tests/libxrpl/csf/Scheduler.h>
#include <tests/libxrpl/csf/SimTime.h>
#include <csf/Scheduler.h>
#include <csf/SimTime.h>
#include <chrono>
#include <iostream>
+1 -1
View File
@@ -1,4 +1,4 @@
#include <tests/libxrpl/helpers/Account.h>
#include <helpers/Account.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
+2 -2
View File
@@ -1,7 +1,5 @@
#pragma once
#include <tests/libxrpl/helpers/Account.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Asset.h>
@@ -9,6 +7,8 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/UintTypes.h>
#include <helpers/Account.h>
#include <string>
#include <string_view>
#include <type_traits>
@@ -1,8 +1,5 @@
#pragma once
#include <tests/libxrpl/helpers/TestNodeFamily.h>
#include <tests/libxrpl/helpers/TestSink.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
@@ -15,6 +12,9 @@
#include <boost/asio/io_context.hpp>
#include <helpers/TestSink.h>
#include <shamap/common.h>
#include <cstdint>
#include <memory>
#include <optional>
+1 -1
View File
@@ -1,4 +1,4 @@
#include <tests/libxrpl/helpers/TestSink.h>
#include <helpers/TestSink.h>
#include <xrpl/beast/utility/Journal.h>
+4 -4
View File
@@ -1,7 +1,4 @@
#include <tests/libxrpl/helpers/TxTest.h>
#include <tests/libxrpl/helpers/Account.h>
#include <tests/libxrpl/helpers/IOU.h>
#include <helpers/TxTest.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
@@ -25,6 +22,9 @@
#include <xrpl/protocol_autogen/transactions/Payment.h>
#include <xrpl/tx/apply.h>
#include <helpers/Account.h>
#include <helpers/IOU.h>
#include <cstdint>
#include <memory>
#include <optional>
+4 -4
View File
@@ -1,9 +1,5 @@
#pragma once
#include <tests/libxrpl/helpers/Account.h>
#include <tests/libxrpl/helpers/IOU.h>
#include <tests/libxrpl/helpers/TestServiceRegistry.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
@@ -27,6 +23,10 @@
#include <xrpl/protocol_autogen/ledger_entries/AccountRoot.h>
#include <xrpl/tx/applySteps.h>
#include <helpers/Account.h>
#include <helpers/IOU.h>
#include <helpers/TestServiceRegistry.h>
#include <cmath>
#include <concepts>
#include <cstdint>
+114
View File
@@ -598,6 +598,91 @@ TEST(JsonValue, compare_strings)
#pragma pop_macro("DO_COMPARE")
}
TEST(JsonValue, construct_from_string_view)
{
using namespace std::string_view_literals;
// Characters are copied with an explicit length, so a view need not be NUL-terminated.
EXPECT_EQ(json::Value{"2.0"sv}.asString(), "2.0");
EXPECT_EQ(json::Value{"2.0 and more"sv.substr(0, 3)}.asString(), "2.0");
EXPECT_EQ(json::Value{""sv}.asString(), "");
// A default-constructed view has a null `data()` and yields an empty string value.
std::string_view const empty;
json::Value const fromEmpty{empty};
EXPECT_TRUE(fromEmpty.isString());
EXPECT_EQ(fromEmpty.asString(), "");
EXPECT_TRUE(fromEmpty == ""sv);
// The view need not outlive the call.
json::Value copied;
{
std::string const owner{"borrowed"};
copied = json::Value{std::string_view{owner}};
}
EXPECT_EQ(copied.asString(), "borrowed");
// A reader of a string value stops at the first NUL, so a view carrying one reads back short.
json::Value const embedded{"a\0b"sv};
EXPECT_EQ(embedded.asString(), "a");
EXPECT_TRUE(embedded != "a\0b"sv);
EXPECT_TRUE(embedded == "a"sv);
EXPECT_TRUE(embedded == json::Value{"a\0b"sv});
}
TEST(JsonValue, compare_string_view)
{
// Comparing against a view builds no temporary Value.
using namespace std::string_view_literals;
EXPECT_TRUE(json::Value{"2.0"} == "2.0"sv);
EXPECT_FALSE(json::Value{"2.0"} != "2.0"sv);
EXPECT_TRUE(json::Value{"2.0"} != "2.00"sv);
EXPECT_TRUE(json::Value{"2.0"} != ""sv);
// A view need not be NUL-terminated, and the comparison must not read past its end.
EXPECT_TRUE(json::Value{"2.0"} == "2.0 and more"sv.substr(0, 3));
EXPECT_TRUE(json::Value{""} == ""sv);
// A string value holding a null pointer equals nothing, not even the empty view.
EXPECT_TRUE(json::Value{json::ValueType::String} != ""sv);
EXPECT_TRUE(json::Value{json::ValueType::String} != "2.0"sv);
EXPECT_TRUE(json::Value{json::ValueType::String} != json::Value{""});
// Nothing that is not a string equals one. Null is what an absent member reads as.
EXPECT_TRUE(json::Value{json::ValueType::Null} != ""sv);
EXPECT_TRUE(json::Value{json::ValueType::Null} != "2.0"sv);
EXPECT_TRUE(json::Value{2} != "2"sv);
EXPECT_TRUE(json::Value{true} != "true"sv);
EXPECT_TRUE(json::Value{json::ValueType::Object} != ""sv);
// Only a view reaches this overload; every other spelling compares through Value.
static constexpr json::StaticString kVersion{"2.0"};
EXPECT_TRUE(json::Value{"2.0"} == kVersion);
EXPECT_TRUE(json::Value{"2.1"} != kVersion);
EXPECT_TRUE(json::Value{} != kVersion);
EXPECT_TRUE(json::Value{"2.0"} == "2.0");
EXPECT_TRUE(json::Value{"2.0"} != "2.1");
EXPECT_TRUE(json::Value{} != "2.0");
EXPECT_TRUE(json::Value{"2.0"} == std::string{"2.0"});
EXPECT_TRUE(json::Value{"2.0"} != std::string{"2.1"});
EXPECT_TRUE(json::Value{json::ValueType::Null} != std::string{"2.0"});
// A literal `0` also converts to a view through `char const*`, so a plain overload would make
// this comparison ambiguous. It must compare as a number: RPCCall.cpp rejects a
// `transaction_entry` ledger index this way.
EXPECT_TRUE(json::Value{0} == 0);
EXPECT_TRUE(json::Value{4294967295u} != 0);
EXPECT_TRUE(json::Value{""} != 0);
EXPECT_TRUE(json::Value{2} != 0);
// A null and an Int are different types, so the comparison is false.
EXPECT_TRUE(json::Value{} != 0);
}
TEST(JsonValue, bool)
{
EXPECT_FALSE(json::Value());
@@ -634,6 +719,35 @@ TEST(JsonValue, bad_json)
EXPECT_TRUE(r.parse(s, j));
}
TEST(JsonValue, reject_null_in_string)
{
// json::Value keeps a string only through its first NUL, so a NUL in the document would make
// "3.0\u0000junk" read as "3.0". The reader refuses it, escaped or raw, in a value or a key.
using namespace std::string_literals;
for (auto const& doc : {
R"JSON({"v": "3.0\u0000junk"})JSON"s,
R"JSON({"v\u0000junk": 1})JSON"s,
R"JSON({"v": "\u0000"})JSON"s,
"{\"v\": \"3.0\0junk\"}"s,
})
{
json::Value j;
json::Reader r;
EXPECT_FALSE(r.parse(doc, j)) << doc;
// The message names the condition, not the text around it.
auto const message = r.getFormattedErrorMessages();
EXPECT_TRUE(message.contains("Null character in string")) << message;
EXPECT_FALSE(message.contains("junk")) << message;
}
// Every other control character still decodes.
json::Value j;
json::Reader r;
ASSERT_TRUE(r.parse(R"JSON({"v": "a\u0001b"})JSON", j));
EXPECT_EQ(j["v"].asString(), "a\x01"s + "b");
}
namespace {
[[nodiscard]] std::optional<json::Value>
+2 -3
View File
@@ -1,13 +1,12 @@
#include <xrpl/ledger/entries/AMMEntry.h>
#include <tests/libxrpl/helpers/IOU.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <gtest/gtest.h>
#include <helpers/IOU.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
@@ -1,11 +1,10 @@
#include <xrpl/ledger/entries/AccountRootEntry.h>
#include <tests/libxrpl/helpers/Account.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <helpers/Account.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
+1 -2
View File
@@ -1,10 +1,9 @@
#include <xrpl/ledger/entries/AmendmentsEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
+1 -2
View File
@@ -1,11 +1,10 @@
#include <xrpl/ledger/entries/CheckEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
+1 -2
View File
@@ -1,12 +1,11 @@
#include <xrpl/ledger/entries/CredentialEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <string>
+1 -2
View File
@@ -1,10 +1,9 @@
#include <xrpl/ledger/entries/DIDEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
+1 -2
View File
@@ -1,11 +1,10 @@
#include <xrpl/ledger/entries/DelegateEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
@@ -1,13 +1,12 @@
#include <xrpl/ledger/entries/DepositPreauthEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <set>
#include <string>
@@ -1,11 +1,10 @@
#include <xrpl/ledger/entries/DirectoryNodeEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
#include <cstdint>
+2 -3
View File
@@ -1,8 +1,5 @@
#pragma once
#include <tests/libxrpl/helpers/Account.h>
#include <tests/libxrpl/helpers/TxTest.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ApplyViewImpl.h>
@@ -11,6 +8,8 @@
#include <xrpl/protocol/LedgerHeader.h>
#include <gtest/gtest.h>
#include <helpers/Account.h>
#include <helpers/TxTest.h>
#include <string>
+1 -2
View File
@@ -1,11 +1,10 @@
#include <xrpl/ledger/entries/EscrowEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SeqProxy.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
+1 -2
View File
@@ -1,5 +1,3 @@
#include <tests/libxrpl/helpers/TestNodeFamily.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/hash/uhash.h>
@@ -15,6 +13,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <gtest/gtest.h>
#include <shamap/common.h>
#include <cstdint>
#include <memory>
@@ -1,10 +1,9 @@
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
#include <tests/libxrpl/ledger/EntryTestHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <gtest/gtest.h>
#include <ledger/EntryTestHelpers.h>
namespace xrpl::test {
Loaded 100 of 262 files, more files were not shown because too many files have changed in this diff. Show more