Compare commits

..
Author SHA1 Message Date
Bart 2a0c8ba982 feat: Accept a JSON-RPC 2.0 batch from API version 3
A top-level JSON array is the batch form the specification defines, accepted
from API version 3. Each entry is a request in its own right, nesting its
parameters as a lone request does and correlating by its own `id`. The
answer is an array of one reply per entry at HTTP 200, as the `method:
"batch"` form answers. All entries of one body must name the same
`api_version`, or name none and inherit the body's, and a body whose entries
disagree is refused whole. For a top-level array the first entry to name
a served version of 3 or above decides, and for a `method: "batch"` body
the first entry to name a version the server serves does, wherever that
entry sits, so the order of the entries does not matter; a `method:
"batch"` body naming a version the server cannot serve at its own top level
is refused rather than served at version 1. A body refused before it has
named a version is answered as version 1, so neither a malformed `params`
nor the first entry decides the shape of the refusal. One body then has
one version, which is what makes a 100-entry cap answerable: a cap that read a
version off one entry could be side-stepped by reordering the entries, or by
naming the version where the cap did not look, so the cap reads the body's
one version, found wherever dispatch would find it.

Refusing a mixed body is a break a version 1 or 2 client can see, recorded
as such in `API-CHANGELOG.md`. A `method: "batch"` body whose entries name
different versions is served today, each entry at its own version, and is
refused from now on, as is one whose top level names a version its entries
disagree with, where the top-level value was ignored; one whose entries
agree, with each other and with any top-level version, keeps working, and
below version 3 that form stays uncapped.

Five conditions reject a whole array: empty, over the cap, holding nothing
that could be a request, naming no served version of 3 or above, and
naming two different served versions across its entries. Four answer one
specification error object with a null `id`, an array naming none; an array
holding nothing that could be a request answers one such object per entry,
as the specification shows. Each reaches every API version, since no earlier
version accepts an array.
2026-10-14 18:42:02 +09:00
Bart 4d30ee7ec3 feat: Return the JSON-RPC 2.0 envelope on the WebSocket transport
The specification envelope reaches only the JSON-RPC transport, so API
version 3 answers in two shapes depending on which transport carried the
request. Shape the WebSocket reply the same way, reading `id` from the flat
message object rather than from `params`. The reply keeps one extra member,
`"type": "response"`, since a session also receives server-initiated
messages on the same connection and `type` is how a client tells the two
apart. The load notice is written inside the result at this version, which
`shapeSpecReply` hoists back to the top level.

Six conditions reject a request before it reaches a handler, and from
version 3 five answer in the specification envelope under the code the
JSON-RPC transport reports for the same condition: a request naming no
method, one naming a method that is not a string, one whose `command` is
not a string, and one naming two different methods are each an invalid
request, and a role the server refuses is forbidden. Those first four
collapse into one bare `missingCommand` token today, so a client naming two
methods is told the command entry is missing. An `id` that is neither a
string, a number nor null is rejected here too, answered with a null `id`
and judged before the role, as on the JSON-RPC transport.

The sixth condition, an unsupported `api_version`, keeps the legacy shape,
since the server cannot know which envelope such a client speaks. Below
version 3 all six are unchanged. The jtx WebSocket client gains `invokeRaw`,
which returns a reply as it arrived, and `invoke` rewrites a version 3 reply
into the legacy shape so a test can assert one shape across versions.
2026-10-14 18:42:02 +09:00
Bart d25df82c14 feat: Return a JSON-RPC 2.0 envelope from API version 3
API version 3 answers the JSON-RPC 2.0 specification's shape on the
JSON-RPC transport: `jsonrpc` and the request's `id` at the top level, the
payload under `result`, and a failure under `error` as {code, message,
data}. Three helpers write it, one for the members every reply carries, one
for a pre-dispatch rejection and one for a handler result, so a completed
request and a rejection name the protocol and correlate with their request
the same way.

An XRPL error is an application-level failure of a well-formed call, so it
reports the reserved implementation-defined code and carries its own token
and code inside `data`. A method the server does not have is the exception:
the specification defines a code for that condition, so the token selects
it. An `id` the specification does not allow is answered with a null one
rather than echoed, and is judged before the role and method checks, so a
client is told about its id whatever it asked for. `reject` charges the fee
itself, and the two rejections that must not charge say so. `removeMember`
moves the erased value out rather than copying it, which `hoistNotices`
relies on for every notice it moves.

Versions 1 and 2 are untouched, the envelope being selected by
`api_version` and version 3 needing `[beta_rpc_api]`. Four jtx helpers let
a test assert one payload shape across every version, and
`xrpl.jtx.TestHelpers` pins them.
2026-10-14 18:42:02 +09:00
Bart 0fe9167bed feat: Read a request's version at its top level from API version 3
The version a request asks for is read from its parameters, where a client
puts it, and for a `method: "batch"` entry from the entry's own top level
too, since that form carries it beside its method. The JSON-RPC 2.0
specification puts a request's own members beside `method`, so a lone
request may spell `api_version` there as well. One helper states where a
request keeps its version, so the two reads share one answer. The sign and
submit paths that read the version out of the parameters again take the
one already selected instead, so a handler and its checks agree.

Two things a client can observe. A lone request's top-level `api_version`
is now honored, but only from version 3 up: a top-level `api_version: 2`
answers as version 1, as does one the server cannot serve, so honoring
either would change a shipped reply shape. And for a `method: "batch"`
entry, the one shape whose top-level version was already read, the
precedence reverses: the value with the parameters now decides, where the
top-level one previously decided whenever the nested one resolved to
version 1.
2026-10-14 18:42:01 +09:00
Bart 2c5cee2925 fix: Serve every subscription on a connection at one API version
`InfoSub` holds one `apiVersion_` field and every `subscribe` or
`path_find` call overwrites it, so a connection subscribing two streams in
two calls at two `api_version`s has both served at whichever ran last. A
webhook subscriber is keyed on its url alone, so two admins naming one url
share one `InfoSub` and each call reselects the other's content. Two changes
together: a subscription records the version its `subscribe` named, so no
publisher reads a version off a shared object, and `doSubscribe` refuses a
`subscribe` naming a version different from the one this connection
established, answering `apiVersionConflict`, at HTTP 400 where the envelope
derives the status from the error, and registering nothing.

Refusing removes an impossible choice at publish time: one message can reach
a connection through several of its subscriptions, and one message has one
shape. The version is established and compared inside `doSubscribe` and
nowhere else, so a check in the general request path would pin every request
a connection sends. The check runs before any stream is registered, so a
`subscribe` refused further down, for a stream name the server does not
have, still fixes the version; the refusal message speaks of what the
subscriber is served at rather than of what it holds. `path_find` records
its version on the `PathRequest` instead; nothing reads that record until a
later commit shapes the update. `InfoSub::assignId` returns the 64-bit
counter it reads, where it narrowed the value to `int`.

A client sees a second `subscribe` at another version refused where it used
to succeed and reshape the first subscription, and a shared webhook url is
now first-writer-wins. An ordinary request is unaffected, naming its own
version and being answered at it.
2026-10-14 18:42:01 +09:00
Bart 450d00ad65 refactor: Take an ErrorCodeI in LedgerEntryHelpers, not a token string
The `ledger_entry` helpers report a `malformed*` token as a bare string a
caller types by hand, so a typo compiles cleanly and is caught only by
`LedgerEntry_test.cpp`'s token-to-code map failing at run time. Take an
`ErrorCodeI` and derive the token from it through `getErrorInfo`, so a
token cannot be misspelled and comes from one table rather than a literal
per call site. Choosing the wrong code remains the caller's
responsibility, the parameter being the whole unscoped enum, and that map
is what catches it.

No reply changes. These helpers still report `RpcInvalidParams` (31) for
every token they name, because a version 1 or 2 client has read 31 for all
of them for years. Reconciling a token with the code that belongs to it
will be the version 3 envelope's job, through `codeForToken`, which a
later branch gives its first caller.

The three helpers that build an error and the nine that forward to them
gain docstrings, replacing the block comment that covered the family. The
guard on `parseDirectoryNode`'s `owner` arm goes: the check above it
requires exactly one of `owner` and `dir_root` and the `dir_root` arm
returns, so the guard was always true and the error after it unreachable.
2026-10-14 18:42:01 +09:00
Bart b515af4d4e feat: Look up the error code that owns a token
One handler reports an error token whose own code it cannot use, because
changing `error_code` breaks clients matching on the old value: the
`ledger_entry` helpers name twenty-one `malformed*` tokens and report
`invalidParams` (31) for all of them. Reconciling that at the reply
envelope needs the reverse of `getErrorInfo`, and twenty of the twenty-one
have no `ErrorCodeI` entry at all, so `getErrorInfo` resolves none of them.
`malformedRequest` is the exception, already holding 107.

Add a code for each of the twenty, alphabetical by token, since an
append-only enum can only be ordered at introduction. Then add
`codeForToken`, which scans the table and answers `RpcUnknown` for a token
no entry names. The scan compares against views measured at compile time
rather than the table's own `char const*`, since comparing a view with a
pointer measures the pointer first and would call strlen on every entry it
passes. A compile-time assertion makes a duplicate token a build error
rather than a lookup reporting one of two codes. Four comments in the two
files spelled `rpcLAST`, `rpcSUCCESS` and `rpcUNKNOWN` where the
enumerators are `RpcLast`, `RpcSuccess` and `RpcUnknown`; they spell the
enumerators now.

Nothing on the wire changes: the helpers still report `invalidParams`, so
this only reserves the numbers a later API version reports. The first
caller of `codeForToken` is the version 3 reply envelope, which a later
branch adds.
2026-10-14 18:42:00 +09:00
Bart cf495fde86 fix: Charge for a request the server rejects before it can read it
Seven conditions reject a request without charging for it. Five reject a
whole body before the server reads a request out of it: over the size
limit, unparsable, carrying no document, not a JSON object, or a
`method: "batch"` naming no entry array. Two sit inside the batch loop: an
entry that is not an object, and one naming an API version the server does
not serve. Free is what makes a rejection worth repeating, so a client can
send nothing else and never exhaust its allowance. The WebSocket transport
has the same hole for a frame that does not parse, which is answered before
`processSession`, where every other frame is charged.

Charge all of them what a malformed request costs: the HTTP conditions
through two helpers over a third naming the resource entry a request pays
from before its role is read, and the WebSocket frame where it is answered.
The role comes from whatever credentials the request presents, so a
privileged connection stays unlimited. Only one helper reports whether the
charge crossed the drop threshold, and it asks only where its caller can
act on the answer: `disconnect` is a mutator that charges the drop fee and
counts a drop on every call made while the balance is at or above the drop
threshold.

The answer to each rejection is unchanged, except that a WebSocket
connection over the drop threshold is closed rather than answered. What
changes is that a `method: "batch"` body now stops at the first entry the
connection is too loaded to serve, where it previously answered every
entry, so the reply array can be shorter than the request array. That form
is uncapped, and one body within the size limit holds around 333,000
entries.
2026-10-14 18:42:00 +09:00
Bart 00bf88afad fix: Accept a request's parameters by name
A JSON-RPC 2.0 request names its parameters in one of two ways: by
position, where `params` is an array holding the one object a handler
reads, and by name, where `params` is that object itself. Only the
positional form is accepted, so the named form is rejected with HTTP 400
and `params unparsable`. Accept both. One helper states where a request
keeps its parameters, so the version read, the role check and the handler
share one answer.

Two things a client can observe. The named `params` form is served, at
every API version, and the `api_version` and credentials it carries are
read as the positional form's are. And a `method: "batch"` entry carrying
a by-name `params` object is read for its version and credentials from
that object; before, only its `api_version` was read from the entry's top
level and its credentials were ignored.
2026-10-14 18:42:00 +09:00
Bart f45dab46d1 fix: Keep the connection's identity for every entry of a batch
`X-User` and the forwarded-for address are assigned by the connection's
header, so they belong to the connection rather than to one entry of a
`method: "batch"` body. The loop clears them in place for an entry whose
own role is neither identified nor proxied, and a `std::string_view`
shortened in place stays shortened, so the first such entry decides them
for every entry after it. A later entry's role is read from that same
value, so it is demoted along with the username.

Read them into two entry-scoped views instead and hand those to the
handler's context, so the clearing reaches only the entry it belongs to. A
lone request is unaffected, there being no entry after it.

What a client can observe: a body whose first entry presents admin
credentials made the second report no username, where the same entry sent
alone reports the connection's. Every entry of one body now reports the
role and username it would report alone.
2026-10-14 18:41:59 +09:00
Bart b87ad0f08e fix: Name the reason a body is rejected before it is read
Four conditions reject a request body before the server reads a request out
of it, and all four answer `Unable to parse request: ` followed by whatever
the reader recorded. Only one is a parse failure, so for the other three
the reader recorded nothing, the text ends at the colon, and the reply
names a cause that is not theirs while giving no reason at all.

Answer each with its own reason instead. A body over the size limit answers
`Request is too large`, a body that does not parse keeps the parse heading
and the reader's reason after it, a body that parses but carries no
document answers `Request is empty`, and a document that is not a JSON
object answers `Request is not a JSON object`. The status stays 400 for all
four, and this reaches every API version, an unreadable body naming none.

Splitting one condition into four also makes the existing order visible:
the size is checked before the parse, so an oversized body is rejected
without being read.
2026-10-14 18:41:59 +09:00
Bart 50c9e6335e 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.

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-14 18:41:59 +09:00
Bart 78709c1d09 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-14 18:41:58 +09:00
Bart b67266aad4 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-14 18:41:58 +09:00
Bart 79a8b4849e 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. `checkErrorValue` checks
`error_code` beside the token and the message, pinning each token's code
and failing on a token it does not know. The server test reaches a coded
error on four of the five handlers. `ledger_accept` reports
`notStandAlone` only on a networked server that is synced, which no
test harness provides, so that arm joins the test list below.

The `submit` and `simulate` arms reporting an internal error take no
coverage exclusion. Those arms are live, reached once
`NetworkOPs::processTransaction` or `Transaction::getJson` throws, and no
injection seam exists today, so the 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-14 18:41:58 +09:00
Bart 49410ad867 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-14 10:14:03 +09:00
Bart 3689dca688 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-14 10:14:02 +09:00
Bart 671a00b1ac 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 and parser message only, 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 uncapped, so the method name,
bounded by the request size limit, 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-14 10:14:02 +09:00
Bart 9096ec03a7 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-14 10:14:02 +09:00
Bart 5fec493960 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-13 18:45:16 +09:00
Bart a6a4fff2aa 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-13 18:45:15 +09:00
BartandBart c2478f509c refactor: Open the acquisition seams its tests need (#8081)
Co-authored-by: Bart <[email protected]>
2026-10-13 03:51:12 +00:00
Mayukha Vadari c908ed7839 fix: Reject malformed conditions in EscrowFinish preflight (#8523) 2026-10-12 20:48:55 +00:00
BartandBart fc57311c81 refactor: Expose a SHAMapAddNode verdict as counts (#8080)
Co-authored-by: Bart <[email protected]>
2026-10-12 20:02:36 +00:00
Mayukha Vadari 818de49264 refactor: Use DepositPreauthEntry everywhere (#8364) 2026-10-12 18:56:14 +00:00
dependabot[bot] 1318b1e58b chore: [DEPENDABOT] bump cxx from 1.0.202 to 1.0.203 in /crates in the rust-dependencies group (#8564)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 17:23:41 +00:00
Ayaz Salikhov 617c816ec8 chore: Fix readability-trailing-comma (#8545) 2026-10-12 13:57:24 +00:00
dependabot[bot] 34fc86f585 ci: [DEPENDABOT] bump the github-actions group across 1 directory with 2 updates (#8562)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 12:59:39 +00:00
Ayaz Salikhov e071f0a28d chore: Fix misc-const-correctness (#8546) 2026-10-12 11:44:06 +00:00
Ayaz Salikhov 8faa624ade chore: Update XRPLF/actions to use pip from python3.14 explicitly (#8560) 2026-10-12 10:06:06 +00:00
646 changed files with 28361 additions and 16848 deletions

No files matched your search

-5
View File
@@ -76,7 +76,6 @@ Checks: "-*,
-readability-redundant-nested-if,
-readability-redundant-qualified-alias,
-readability-static-accessed-through-instance,
-readability-trailing-comma,
-readability-trivial-switch,
-readability-uppercase-literal-suffix
"
@@ -93,10 +92,6 @@ CheckOptions:
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
# New in clang-tidy 23; disabled until the code is updated
misc-const-correctness.AnalyzeAutoVariables: false
misc-const-correctness.AnalyzeLambdas: false
misc-const-correctness.AnalyzeParameters: false
misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/.*;openssl/obj_mac\\.h"
readability-braces-around-statements.ShortStatementLines: 2
@@ -174,8 +174,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
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
- name: Create issue
if: ${{ steps.audit.outcome != 'success' && github.event_name == 'schedule' }}
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
uses: XRPLF/actions/create-issue@b489c21d53928f7dab4874912fec40e4f6c9e85e
with:
title: "cargo audit found vulnerabilities"
body_file: /tmp/cargo-audit-issue.md
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
- name: Create issue
if: ${{ failure() && github.event_name == 'schedule' && github.repository == 'XRPLF/rippled' }}
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
uses: XRPLF/actions/create-issue@b489c21d53928f7dab4874912fec40e4f6c9e85e
with:
title: "Releases not merged back into develop"
body_file: ${{ env.ISSUE_FILE }}
+2 -2
View File
@@ -79,7 +79,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: false
@@ -108,7 +108,7 @@ jobs:
- name: Upload regenerated snapshot
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: check-tools-${{ runner.os }}-${{ runner.arch }}
path: ${{ matrix.snapshot }}
+1 -1
View File
@@ -14,7 +14,7 @@ on:
jobs:
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
run-hooks:
uses: XRPLF/actions/.github/workflows/pre-commit.yml@279ec358f4a1be4088be3e024b07916fa97c75b6
uses: XRPLF/actions/.github/workflows/pre-commit.yml@d7f2acb47d9662e1e2f8b1f5feffa6d21cc3290a
with:
runs_on: ubuntu-latest
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-70b8fd3" }'
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: false
@@ -129,7 +129,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: ${{ inputs.ccache_enabled }}
@@ -280,7 +280,7 @@ jobs:
- name: Upload the binary (Linux)
if: ${{ env.PACKAGING_ARTIFACTS_ENABLED == 'true' && runner.os == 'Linux' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: xrpld-${{ inputs.config_name }}
path: ${{ env.BUILD_DIR }}/xrpld
@@ -294,7 +294,7 @@ jobs:
- name: Upload the validator-keys binary
if: ${{ env.PACKAGING_ARTIFACTS_ENABLED == 'true' && env.VALIDATOR_KEYS_ENABLED == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: validator-keys-${{ inputs.config_name }}
path: |
@@ -305,7 +305,7 @@ jobs:
- name: Upload the test binary (Linux)
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: xrpl_tests-${{ inputs.config_name }}
path: ${{ env.BUILD_DIR }}/xrpl_tests
@@ -321,7 +321,7 @@ jobs:
- name: Upload server definitions
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'ubuntu-gcc-debug-amd64-coverage' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: server-definitions
path: ${{ env.BUILD_DIR }}/server_definitions.json
+4 -4
View File
@@ -43,7 +43,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: false
@@ -109,7 +109,7 @@ jobs:
- name: Upload clang-tidy output
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
path: ${{ env.OUTPUT_FILE }}
archive: false
@@ -133,7 +133,7 @@ jobs:
- name: Upload clang-tidy diff output
if: ${{ github.event.repository.visibility == 'public' && steps.files_changed.outcome != 'success' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
path: ${{ env.DIFF_FILE }}
archive: false
@@ -185,7 +185,7 @@ jobs:
- name: Create issue
if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }}
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
uses: XRPLF/actions/create-issue@b489c21d53928f7dab4874912fec40e4f6c9e85e
with:
title: "Clang-tidy check failed"
body_file: ${{ env.ISSUE_FILE }}
@@ -44,7 +44,7 @@ jobs:
# Every package lands in one directory; the step below picks its own,
# which keeps this independent of the artifact names.
- name: Download package artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: "*-pkg"
merge-multiple: true
+9 -9
View File
@@ -100,18 +100,18 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: false
- name: Download pre-built xrpld binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ matrix.xrpld_artifact_name }}
path: ${{ env.BUILD_DIR }}
- name: Download pre-built validator-keys binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ matrix.validator_keys_artifact_name }}
path: ${{ env.BUILD_DIR }}
@@ -151,7 +151,7 @@ jobs:
# version starts with a digit and a longer name does not, so that one digit
# is what tells 'xrpld-3.4.1-...' from 'xrpld-assert-3.4.1-...'.
- name: Upload package artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ matrix.xrpld_artifact_name }}-pkg
path: |
@@ -160,7 +160,7 @@ jobs:
if-no-files-found: error
- name: Upload debug symbol artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ matrix.xrpld_artifact_name }}-pkg-debug
path: |
@@ -225,13 +225,13 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: false
# Both artifacts, so the debug symbols are published alongside the package.
- name: Download package artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: ${{ matrix.xrpld_artifact_name }}-pkg*
merge-multiple: true
@@ -287,7 +287,7 @@ jobs:
run: echo "PUSH=${PUSH}" | tee -a "${GITHUB_ENV}"
- name: Download package artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: "*-pkg"
merge-multiple: true
@@ -295,7 +295,7 @@ jobs:
- name: Download voidstar binary
if: ${{ matrix.target == 'voidstar' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: xrpld-ubuntu-clang-debug-amd64-voidstar
path: ${{ env.CONTEXT }}
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: false
+1 -1
View File
@@ -68,7 +68,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
uses: XRPLF/actions/prepare-runner@8e98d61f011fd2a67f3fde5f0f075662c099a527
with:
enable_ccache: false
+31
View File
@@ -22,6 +22,37 @@ 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
- 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. Two things it does not reach: a WebSocket session, which reads the field only to echo it back, and API version 3, where the field selects nothing and so is neither read nor checked, `ripplerpc: "banana"` and no `ripplerpc` at all being served alike. 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.
- `subscribe`: every subscription a connection holds must name the same `api_version`. The first `subscribe` establishes it, and a later one naming a different version is refused with `apiVersionConflict`, registering nothing; the message names the version the connection's subscriptions are served at. The HTTP status is 400 where the envelope derives it from the error, with `ripplerpc: "3.0"` or at API version 3, and 200 with `ripplerpc` `"1.0"` or `"2.0"`, as for every error; a WebSocket frame carries no status. A `subscribe` refused for another reason, a stream name the server does not have for one, still fixes that version, since the registrations it makes are spread through the handler. A connection that subscribed one stream at `api_version` 2 and another at 1, in two calls, was previously served both at version 1, the later call's, so the first stream's shape changed under it. The version is fixed for the connection's life: a client that wants another version opens another connection. On the webhook path the subscriber is keyed on its `url` alone, so the refusal reaches a second admin because of a first admin's version, and the message names the version the url's subscriptions are served at, and no remedy: releasing a url means unsubscribing its streams, which would remove that first admin's subscription, and the server cannot tell the two callers apart. Naming the url alone does not release it, a subscriber a stream map still holds not being evicted. This reaches every API version, since version 1 and version 2 content is not interchangeable either: version 2 renames `transaction` to `tx_json` and hoists `hash`, so a version 1 client handed version 2 content finds no `transaction` member.
- `batch`: An entry of a `"method": "batch"` request that names `api_version` both inside `params` and at its own top level is now served at the version inside `params`. It was previously served at the top-level version whenever the one inside `params` resolved to version 1, so an entry asking for version 1 explicitly and something else at the top level changes which version answers it. A lone request can name both values too, and for it the version inside `params` has always won; reversing that precedence was confined to a batch entry.
- `batch`: every entry of one batch request is now served at the same API version. An entry may name the version the body names, or name none and inherit it, and a body whose entries name two different versions is refused whole with `Batch entries name different versions` at HTTP 400, charged as a malformed request. **This is visible to a version 1 or 2 client:** a `"method": "batch"` body could mix versions across its entries, each entry being dispatched at its own, and the difference is observable because the reply shape changes with the version. Three bodies change. One whose entries name two different versions is now refused instead of answered. One where some entries name a version and others name none now serves all of them at the named version, where an entry naming none was served at version 1; the same applies to a version named on the request's own top level, which the entries now inherit. One whose top level names a version and whose entries name a different one is now refused, where the top-level value was ignored and each entry was served at the version it named. A body whose top level names no version or the same version as its entries, and whose entries agree with one another or name none at all, is unaffected. A `"method": "batch"` body naming at its own top level a version the server does not serve is refused with `invalid_API_version` at HTTP 400, where it was previously served at version 1 as if it had named none. No test pinned the mixing behavior and the line that produced it was a fallback about where the field sits rather than a decision that entries may differ, so this is a correction rather than a removal of a feature: a per-entry version made the entry cap above unanswerable, since a cap can only ask about a body that has one version.
### Additions
- A JSON-RPC (HTTP) request may send its parameters as an object, `"params": {"account": "r..."}`, as well as the array of one object that was already accepted. This reaches every API version: such a request was previously rejected with HTTP 400 and `params unparsable`, and is now served. A request that already sends the array form is unaffected. An entry of a `"method": "batch"` request that carries a by-name `params` object is read for `api_version` and credentials from that object. Previously such an entry had its `api_version` read from its top level and its credentials ignored, since credentials were read only from an array-form `params`.
- A JSON-RPC (HTTP) request may name its `api_version` at its own top level, beside `method` and `id`, as well as inside `params`. The member is an XRPL extension, which the JSON-RPC 2.0 specification does not define; placing it beside the specification's own members is what lets a version 3 client send a specification-shaped request. This is honored from API version 3 up, so it needs `[beta_rpc_api]`; a version below that named at the top level is ignored, exactly as it always has been. Previously only an entry of a `"method": "batch"` request had its own top level read; a WebSocket request, which is one flat object, has always been read there.
### Bugfixes
- A request echoed back in an error reply now has every credential-bearing field masked: `admin_password`, `admin_user`, `passphrase`, `password`, `secret`, `seed`, `seed_hex`, `url_password`, `url_username` and `username`. Nesting no longer matters, so a credential inside `params` is masked too. The same masking is applied to every request and reply written to the log, and it covers six further names that only a reply carries: `master_key`, `master_seed`, `master_seed_hex`, `validation_key`, `validation_private_key` and `validation_seed`, which is how `wallet_propose` and `validation_create` used to write a live private key to the log. 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 and the parser's message, never by its text, 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. **Only API version 3 reports these four.** A request sending `ripplerpc: "3.0"` at API version 1 or 2 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 for those clients 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"`, or API version 3, which are the envelopes that derive 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`. From API version 3 the twenty-one `malformed*` tokens `ledger_entry` names each report the code they own instead of 31.
- 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`, `sign_for` 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.
- A body the server rejects before it reads a request out of it now says what was wrong. A body over the size limit answers `Request is too large`, and a body that parses to `{}` or `null` answers `Request is empty`; both previously answered `Unable to parse request: ` with nothing after the colon, the parser having recorded no error for them. Any other body that is not an array does not parse, which includes one that is only whitespace and one whose top-level value is a string, number or boolean; it answers `Unable to parse request: ` followed by the parser's own reason, as it did before. The status is 400 for all three, as before, and this reaches every API version. A top-level array, the one other body nothing can read a request from, is answered with a JSON error object instead, which the entry on top-level arrays below describes.
- `batch`: An entry that is not identified through a secure gateway no longer clears the connection's `X-User` and forwarded-for values for the entries after it, so every entry of one body reports the role and username it would have reported on its own.
- `batch`: Every entry of a `"method": "batch"` request is now charged against the sender's resource allowance, including one rejected before it reaches a handler, and the request stops at the first entry the connection is too loaded to serve. A reply array can therefore be shorter than the request array, and its last element depends on where the batch stopped. An entry that is not a JSON object, or names an API version the server does not serve, is charged before its role is known and answered with its own rejection; if that charge took the connection over the drop threshold, the batch stops there and that rejection is the last answer. Every other entry met over the threshold is answered `Server is overloaded` and nothing follows. A client should read any reply array shorter than its request as a connection over the drop threshold, whatever the last element says. A well-behaved client is unaffected; one that sends thousands of entries in a single body no longer gets every one of them answered.
- A body the server rejects before it reads a request out of it is now charged against the sender's resource allowance, as a malformed request already was. Ten conditions were free: a body over the size limit, one that does not parse, one carrying no document, one that is neither a JSON object nor an array, a `"method": "batch"` naming no entry array, and six ways a top-level array is not a batch this server can serve. The answer to the first five is unchanged; an array's answer changes as the entry on top-level arrays below describes, the one generic rejection it received having become six. A well-behaved client is unaffected; one that repeats such a body exhausts its allowance and is refused the next request a handler would have served. A WebSocket frame that does not parse, or exceeds the request size limit, is charged the same way, and a connection that sends only such frames is closed once it crosses the drop threshold.
- `subscribe`, `path_find`: A subscription is now served at the API version its own `subscribe` call named, rather than at the version of the most recent `subscribe` or `path_find` on the same connection. A `path_find` no longer changes the version anything that connection has subscribed is served at. An ordinary request is unaffected: it names its own version and is answered at it, so a connection subscribed at `api_version` 3 still calls `account_info` at version 1 and reads the version 1 shape.
- `batch`: A batch request served at API version 3 or above is now refused when it holds more than 100 entries, before any entry is dispatched. The cap reads the one version the body is served at, so no ordering of the entries evades it. A body served below version 3 is unaffected and stays bounded only by the request size limit.
- A top-level JSON array, which is the JSON-RPC 2.0 batch form accepted from API version 3, is now rejected with a JSON-RPC error object rather than a plain text body when it cannot be served: when it is empty, holds more than 100 entries, holds nothing that could be a request, names no API version of 3 or above that the server serves, or names two different versions the server serves. An array naming no served version of 3 or above is refused for requiring version 3, or, where every version it names is one the server does not serve, with `invalid_API_version`. One entry naming a version the server does not serve does not reject an array in which another entry names a served version: it is answered on its own, inside the reply array, with the same `-32606` code. This reaches every API version, since no earlier version accepts an array at all: a client that sent one received HTTP 400 with `Unable to parse request: ` and now receives the same status with a JSON body. The message also names the version rather than the shape where the version is what is wrong, which is what a client with `[beta_rpc_api]` disabled receives.
## XRP Ledger server version 3.5.0
Version 3.5.0 is not yet released.
+10 -10
View File
@@ -57,9 +57,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.202"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13f6de320895f42e6e081abb5c7983bedcf0b6d0ff9323de0d33f620c8ac1199"
checksum = "c8a02bfeba9074b2bbb39492c9b0908ec854f455a3cccdcde7a1de55e2315e40"
dependencies = [
"cc",
"cxx-build",
@@ -72,9 +72,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.202"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fde53ca86b9704a943fef0f1e1d836239a6aedca5de3c65fa9f97ec0bd46d39"
checksum = "bbc2ac76997619d2d888b58ad79488f1c835de41ca4dd3dd7082acf4ecb0a50f"
dependencies = [
"cc",
"codespan-reporting",
@@ -87,9 +87,9 @@ dependencies = [
[[package]]
name = "cxxbridge-cmd"
version = "1.0.202"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bae89236c811fd4d08ed3441759ac4ac98d752cbfd3de341315ba16ad20ec3"
checksum = "54a486ed16a3f2a053fe7f5b98db4c1a59846a23e8cb9e49f9d1bd48830d51a2"
dependencies = [
"clap",
"codespan-reporting",
@@ -101,15 +101,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.202"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49045042e5fced01b80742aba5508de82aa4f13677ed3fa2b4cda709c40c5918"
checksum = "c72569be94e2283764d334cd285980b747dbe221e27e315484eb6bcd93fa808a"
[[package]]
name = "cxxbridge-macro"
version = "1.0.202"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b181252e2e3d3b5d183afbdc033a3958b445eb3e1a0ae65fc3d4f5259f5da6fd"
checksum = "423e795284a7f7b3f843c956a5322c4bc497b60c2dd1a6339da10313cb18d575"
dependencies = [
"indexmap",
"proc-macro2",
-118
View File
@@ -1,118 +0,0 @@
#pragma once
#include <functional>
#include <mutex>
#include <set>
/**
* RAII class to check if an Item is already being processed on another thread,
* as indicated by it's presence in a Collection.
*
* If the Item is not in the Collection, it will be added under lock in the
* ctor, and removed under lock in the dtor. The object will be considered
* "usable" and evaluate to `true`.
*
* If the Item is in the Collection, no changes will be made to the collection,
* and the CanProcess object will be considered "unusable".
*
* It's up to the caller to decide what "usable" and "unusable" mean. (e.g.
* Process or skip a block of code, or set a flag.)
*
* The current use is to avoid lock contention that would be involved in
* processing something associated with the Item.
*
* Examples:
*
* void IncomingLedgers::acquireAsync(LedgerHash const& hash, ...)
* {
* if (CanProcess check{acquiresMutex_, pendingAcquires_, hash})
* {
* acquire(hash, ...);
* }
* }
*
* bool
* NetworkOPsImp::recvValidation(
* std::shared_ptr<STValidation> const& val,
* std::string const& source)
* {
* CanProcess check(
* validationsMutex_, pendingValidations_, val->getLedgerHash());
* BypassAccept bypassAccept =
* check ? BypassAccept::no : BypassAccept::yes;
* handleNewValidation(app_, val, source, bypassAccept, m_journal);
* }
*
*/
class CanProcess
{
public:
template <class Mutex, class Collection, class Item>
CanProcess(Mutex& mtx, Collection& collection, Item const& item)
: cleanup_(insert(mtx, collection, item))
{
}
~CanProcess()
{
if (cleanup_)
cleanup_();
}
CanProcess(CanProcess const&) = delete;
CanProcess&
operator=(CanProcess const&) = delete;
explicit
operator bool() const
{
return static_cast<bool>(cleanup_);
}
private:
template <bool useIterator, class Mutex, class Collection, class Item>
std::function<void()>
doInsert(Mutex& mtx, Collection& collection, Item const& item)
{
std::unique_lock<Mutex> lock(mtx);
// TODO: Use structured binding once LLVM 16 is the minimum supported
// version. See also: https://github.com/llvm/llvm-project/issues/48582
// https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
auto const insertResult = collection.insert(item);
auto const it = insertResult.first;
if (!insertResult.second)
return {};
if constexpr (useIterator)
return [&, it]() {
std::unique_lock<Mutex> lock(mtx);
collection.erase(it);
};
else
return [&]() {
std::unique_lock<Mutex> lock(mtx);
collection.erase(item);
};
}
// Generic insert() function doesn't use iterators because they may get
// invalidated
template <class Mutex, class Collection, class Item>
std::function<void()>
insert(Mutex& mtx, Collection& collection, Item const& item)
{
return doInsert<false>(mtx, collection, item);
}
// Specialize insert() for std::set, which does not invalidate iterators for
// insert and erase
template <class Mutex, class Item>
std::function<void()>
insert(Mutex& mtx, std::set<Item>& collection, Item const& item)
{
return doInsert<true>(mtx, collection, item);
}
// If set, then the item is "usable"
std::function<void()> cleanup_;
};
+1 -1
View File
@@ -34,7 +34,7 @@ lz4Compress(void const* in, std::size_t inSize, BufferFactory&& bf)
// data
auto compressed = bf(outCapacity);
auto compressedSize = LZ4_compress_default(
auto const compressedSize = LZ4_compress_default(
reinterpret_cast<char const*>(in),
reinterpret_cast<char*>(compressed),
inSize,
+38 -30
View File
@@ -20,12 +20,14 @@ SharedIntrusive<T>::SharedIntrusive(T* p, TAdoptTag) noexcept : ptr_{p}
template <class T>
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive const& rhs)
: ptr_{[&] {
auto p = rhs.unsafeGetRawPtr();
if (p)
p->addStrongRef();
return p;
}()}
: ptr_{
[&] {
auto p = rhs.unsafeGetRawPtr();
if (p)
p->addStrongRef();
return p;
}(),
}
{
}
@@ -33,12 +35,14 @@ template <class T>
template <class TT>
requires std::convertible_to<TT*, T*>
SharedIntrusive<T>::SharedIntrusive(SharedIntrusive<TT> const& rhs)
: ptr_{[&] {
auto p = rhs.unsafeGetRawPtr();
if (p)
p->addStrongRef();
return p;
}()}
: ptr_{
[&] {
auto p = rhs.unsafeGetRawPtr();
if (p)
p->addStrongRef();
return p;
}(),
}
{
}
@@ -140,12 +144,14 @@ SharedIntrusive<T>::~SharedIntrusive()
template <class T>
template <class TT>
SharedIntrusive<T>::SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs)
: ptr_{[&] {
auto p = static_cast<T*>(rhs.unsafeGetRawPtr());
if (p)
p->addStrongRef();
return p;
}()}
: ptr_{
[&] {
auto p = static_cast<T*>(rhs.unsafeGetRawPtr());
if (p)
p->addStrongRef();
return p;
}(),
}
{
}
@@ -159,12 +165,14 @@ SharedIntrusive<T>::SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusiv
template <class T>
template <class TT>
SharedIntrusive<T>::SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs)
: ptr_{[&] {
auto p = dynamic_cast<T*>(rhs.unsafeGetRawPtr());
if (p)
p->addStrongRef();
return p;
}()}
: ptr_{
[&] {
auto p = dynamic_cast<T*>(rhs.unsafeGetRawPtr());
if (p)
p->addStrongRef();
return p;
}(),
}
{
}
@@ -260,7 +268,7 @@ SharedIntrusive<T>::unsafeReleaseAndStore(T* next)
return;
using enum ReleaseStrongRefAction;
auto action = prev->releaseStrongRef();
auto const action = prev->releaseStrongRef();
switch (action)
{
case NoOp:
@@ -361,7 +369,7 @@ WeakIntrusive<T>::unsafeReleaseNoStore()
return;
using enum ReleaseWeakRefAction;
auto action = ptr_->releaseWeakRef();
auto const action = ptr_->releaseWeakRef();
switch (action)
{
case NoOp:
@@ -580,7 +588,7 @@ SharedWeakUnion<T>::convertToStrong()
auto p = unsafeGetRawPtr();
if (p && p->checkoutStrongRefFromWeak())
{
[[maybe_unused]] auto action = p->releaseWeakRef();
[[maybe_unused]] auto const action = p->releaseWeakRef();
XRPL_ASSERT(
(action == ReleaseWeakRefAction::NoOp),
"xrpl::SharedWeakUnion::convertToStrong : "
@@ -603,7 +611,7 @@ SharedWeakUnion<T>::convertToWeak()
return false;
using enum ReleaseStrongRefAction;
auto action = p->addWeakReleaseStrongRef();
auto const action = p->addWeakReleaseStrongRef();
switch (action)
{
case NoOp:
@@ -670,7 +678,7 @@ SharedWeakUnion<T>::unsafeReleaseNoStore()
if (isStrong())
{
using enum ReleaseStrongRefAction;
auto strongAction = p->releaseStrongRef();
auto const strongAction = p->releaseStrongRef();
switch (strongAction)
{
case NoOp:
@@ -688,7 +696,7 @@ SharedWeakUnion<T>::unsafeReleaseNoStore()
else
{
using enum ReleaseWeakRefAction;
auto weakAction = p->releaseWeakRef();
auto const weakAction = p->releaseWeakRef();
switch (weakAction)
{
case NoOp:
+2 -2
View File
@@ -406,10 +406,10 @@ IntrusiveRefCounts::useCount() const noexcept
inline IntrusiveRefCounts::~IntrusiveRefCounts() noexcept
{
#ifndef NDEBUG
auto v = refCounts_.load(std::memory_order_acquire);
auto const v = refCounts_.load(std::memory_order_acquire);
XRPL_ASSERT(
(!(v & kValueMask)), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : count must be zero");
auto t = v & kTagMask;
auto const t = v & kTagMask;
XRPL_ASSERT((!t || t == kTagMask), "xrpl::IntrusiveRefCounts::~IntrusiveRefCounts : valid tag");
#endif
}
+1 -1
View File
@@ -340,7 +340,7 @@ public:
for (auto const& c : cfg)
{
auto& a = allocators_.emplace_back(c.extra_, c.alloc_, c.align_);
auto const& a = allocators_.emplace_back(c.extra_, c.alloc_, c.align_);
if (a.size() > maxSize_)
maxSize_ = a.size();
+7 -7
View File
@@ -300,7 +300,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
// Remove from cache, if !valid, remove from map too. Returns true if removed from cache
std::scoped_lock const lock(mutex_);
auto cit = cache_.find(key);
auto const cit = cache_.find(key);
if (cit == cache_.end())
return false;
@@ -356,7 +356,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
std::scoped_lock const lock(mutex_);
auto cit = cache_.find(key);
auto const cit = cache_.find(key);
if (cit == cache_.end())
{
@@ -371,7 +371,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
Entry& entry = cit->second;
entry.touch(clock_.now());
auto shouldReplaceCached = [&] {
auto const shouldReplaceCached = [&] {
if constexpr (replaceCached)
{
return true;
@@ -400,7 +400,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
return true;
}
auto cachedData = entry.lock();
auto const cachedData = entry.lock();
if (cachedData)
{
@@ -563,7 +563,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
retrieve(key_type const& key, T& data)
{
// retrieve the value of the stored data
auto entry = fetch(key);
auto const entry = fetch(key);
if (!entry)
return false;
@@ -687,7 +687,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
return ret;
}
auto sle = h();
auto const sle = h();
if (!sle)
return {};
@@ -713,7 +713,7 @@ inline SharedPointerType
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
initialFetch(key_type const& key, std::scoped_lock<MutexType> const& l)
{
auto cit = cache_.find(key);
auto const cit = cache_.find(key);
if (cit == cache_.end())
return {};
+3 -2
View File
@@ -195,7 +195,8 @@ private:
{
// Local lambda that converts a single hex char to four bits and
// ORs those bits into a uint32_t.
auto hexCharToUInt = [](char c, std::uint32_t shift, std::uint32_t& accum) -> ParseResult {
auto const hexCharToUInt =
[](char c, std::uint32_t shift, std::uint32_t& accum) -> ParseResult {
std::uint32_t nibble = 0xFFu;
if (c < '0' || c > 'f')
return ParseResult::BadChar;
@@ -453,7 +454,7 @@ public:
{
for (int i = kWidth - 1; i >= 0; --i)
{
auto prev = data_[i];
auto const prev = data_[i];
data_[i] = boost::endian::native_to_big(boost::endian::big_to_native(data_[i]) - 1);
if (prev != 0)
@@ -365,7 +365,7 @@ public:
size() const
{
std::size_t ret = 0;
for (auto& p : map_)
for (auto const& p : map_)
ret += p.size();
return ret;
}
@@ -2536,7 +2536,7 @@ AgedUnorderedContainer<IsMulti, IsMap, Key, T, Clock, Hash, KeyEqual, Allocator>
return false;
for (auto iter(cbegin()), last(cend()), otherLast(other.cend()); iter != last; ++iter)
{
auto otherIter(other.find(extract(*iter)));
auto const otherIter(other.find(extract(*iter)));
if (otherIter == otherLast)
return false;
}
+1 -1
View File
@@ -68,7 +68,7 @@ struct LexicalCast<Out, std::string_view>
if (first != last && *first == '+')
++first;
auto ret = std::from_chars(first, last, out);
auto const ret = std::from_chars(first, last, out);
return ret.ec == std::errc() && ret.ptr == last;
}
+1 -1
View File
@@ -83,7 +83,7 @@ trimRight(String const& s)
{
using std::begin;
using std::end;
auto first(begin(s));
auto const first(begin(s));
auto last(end(s));
last = trimRight(first, last);
return {first, last};
+1 -1
View File
@@ -32,7 +32,7 @@ public:
Module,
// Match nothing (used internally)
None
None,
};
private:
+1 -1
View File
@@ -24,7 +24,7 @@ enum class Severity : std::uint8_t {
Fatal = 5,
Disabled = 6,
None = Disabled
None = Disabled,
};
/**
+1 -1
View File
@@ -18,7 +18,7 @@ enum class Type : std::uint8_t {
PrefixSha256 = 1,
ThresholdSha256 = 2,
RsaSha256 = 3,
Ed25519Sha256 = 4
Ed25519Sha256 = 4,
};
class Condition
@@ -49,7 +49,7 @@ public:
using namespace der;
auto p = parsePreamble(s, ec);
auto const p = parsePreamble(s, ec);
if (ec)
return nullptr;
+1 -1
View File
@@ -21,7 +21,7 @@ enum class Error {
UnexpectedTag,
LongTag,
LargeSize,
PreimageTooLong
PreimageTooLong,
};
std::error_code
+1 -1
View File
@@ -396,7 +396,7 @@ inline bool
getIfExists<bool>(Section const& section, std::string const& name, bool& v)
{
int intVal = 0;
auto stat = getIfExists(section, name, intVal);
auto const stat = getIfExists(section, name, intVal);
if (stat)
v = bool(intVal);
return stat;
+2 -2
View File
@@ -93,14 +93,14 @@ public:
void
check(std::vector<TxID> accepted, Predicate&& pred)
{
auto acceptTxid = accepted.begin();
auto const acceptTxid = accepted.begin();
auto const ae = accepted.end();
std::sort(acceptTxid, ae);
// We want to remove all tracking entries for transactions that were
// accepted as well as those which match the predicate.
auto i = removeIfIntersectOrMatch(
auto const i = removeIfIntersectOrMatch(
tracker_.begin(),
tracker_.end(),
accepted.begin(),
+10 -9
View File
@@ -779,7 +779,7 @@ Consensus<Adaptor>::peerProposalInternal(
{
// update current position
auto peerPosIt = currPeerPositions_.find(peerID);
auto const peerPosIt = currPeerPositions_.find(peerID);
if (peerPosIt != currPeerPositions_.end())
{
@@ -1110,7 +1110,7 @@ Consensus<Adaptor>::checkLedger(std::unique_ptr<std::stringstream> const& clog)
{
CLOG(clog) << "checkLedger. ";
auto netLgr = adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
auto const netLgr = adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
CLOG(clog) << "network ledgerid " << netLgr << ", "
<< "previous ledger " << prevLedgerID_ << ". ";
@@ -1159,8 +1159,8 @@ Consensus<Adaptor>::phaseOpen(std::unique_ptr<std::stringstream> const& clog)
// it is shortly before ledger close time
bool const anyTransactions = adaptor_.hasOpenTransactions();
auto proposersClosed = currPeerPositions_.size();
auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
auto const proposersClosed = currPeerPositions_.size();
auto const proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
openTime_.tick(clock_.now());
@@ -1669,7 +1669,7 @@ Consensus<Adaptor>::haveConsensus(std::unique_ptr<std::stringstream> const& clog
// CHECKME: should possibly count unacquired TX sets as disagreeing
int agree = 0, disagree = 0;
auto ourPosition = result_->position.position();
auto const ourPosition = result_->position.position();
// Count number of agreements/disagreements with our position
for (auto const& [nodeId, peerPos] : currPeerPositions_)
@@ -1686,7 +1686,7 @@ Consensus<Adaptor>::haveConsensus(std::unique_ptr<std::stringstream> const& clog
++disagree;
}
}
auto currentFinished = adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
auto const currentFinished = adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree;
@@ -1812,7 +1812,7 @@ Consensus<Adaptor>::createDisputes(TxSetT const& o, std::unique_ptr<std::strings
<< ". ";
JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to " << o.id();
auto differences = result_->txns.compare(o);
auto const differences = result_->txns.compare(o);
int dc = 0;
@@ -1826,7 +1826,7 @@ Consensus<Adaptor>::createDisputes(TxSetT const& o, std::unique_ptr<std::strings
"xrpl::Consensus::createDisputes : has disputed transactions");
TxT const tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
auto txID = tx.id();
auto const txID = tx.id();
if (result_->disputes.find(txID) != result_->disputes.end())
continue;
@@ -1837,7 +1837,8 @@ Consensus<Adaptor>::createDisputes(TxSetT const& o, std::unique_ptr<std::strings
tx,
result_->txns.exists(txID),
std::max(prevProposers_, currPeerPositions_.size()),
j_};
j_,
};
// Update all of the available peer's votes on the disputed transaction
for (auto const& [nodeId, peerPos] : currPeerPositions_)
+16 -8
View File
@@ -145,18 +145,26 @@ struct ConsensusParms
std::map<AvalancheState, AvalancheCutoff> const avalancheCutoffs{
// {state, {time, percent, nextState}},
// Initial state: 50% of nodes must vote yes
{AvalancheState::Init,
{.consensusTime = 0, .consensusPct = 50, .next = AvalancheState::Mid}},
{
AvalancheState::Init,
{.consensusTime = 0, .consensusPct = 50, .next = AvalancheState::Mid},
},
// mid-consensus starts after 50% of the previous round time, and
// requires 65% yes
{AvalancheState::Mid,
{.consensusTime = 50, .consensusPct = 65, .next = AvalancheState::Late}},
{
AvalancheState::Mid,
{.consensusTime = 50, .consensusPct = 65, .next = AvalancheState::Late},
},
// late consensus starts after 85% time, and requires 70% yes
{AvalancheState::Late,
{.consensusTime = 85, .consensusPct = 70, .next = AvalancheState::Stuck}},
{
AvalancheState::Late,
{.consensusTime = 85, .consensusPct = 70, .next = AvalancheState::Stuck},
},
// we're stuck after 2x time, requires 95% yes votes
{AvalancheState::Stuck,
{.consensusTime = 200, .consensusPct = 95, .next = AvalancheState::Stuck}},
{
AvalancheState::Stuck,
{.consensusTime = 200, .consensusPct = 95, .next = AvalancheState::Stuck},
},
};
/**
+3 -3
View File
@@ -59,7 +59,7 @@ enum class ConsensusMode {
* if we entered the round observing, but is used to indicate we did
* have the wrongLedger at some point.
*/
SwitchedLedger
SwitchedLedger,
};
inline std::string
@@ -222,7 +222,7 @@ medianCloseOffset(ConsensusCloseTimes const& times)
// Accumulate weight in time order; the first bin to reach halfWeight is
// the (lower) weighted median. Returns true once that bin is found.
auto step = [&](time_point t, std::int64_t w) {
auto const step = [&](time_point t, std::int64_t w) {
XRPL_ASSERT(tally < halfWeight, "xrpl::medianCloseOffset::step : median not yet found");
tally += w;
if (tally >= halfWeight)
@@ -267,7 +267,7 @@ enum class ConsensusState {
No, ///< We do not have consensus
MovedOn, ///< The network has consensus without us
Expired, ///< Consensus time limit has hard-expired
Yes ///< We have consensus along with the network
Yes, ///< We have consensus along with the network
};
/**
+1 -1
View File
@@ -266,7 +266,7 @@ template <class Tx, class NodeId>
void
DisputedTx<Tx, NodeId>::unVote(NodeId const& peer)
{
auto it = votes_.find(peer);
auto const it = votes_.find(peer);
if (it != votes_.end())
{
+1 -1
View File
@@ -211,7 +211,7 @@ struct Node
void
erase(Node const* child)
{
auto it = std::ranges::find_if(
auto const it = std::ranges::find_if(
children, [child](std::unique_ptr<Node> const& curr) { return curr.get() == child; });
XRPL_ASSERT(it != children.end(), "xrpl::Node::erase : valid input");
std::swap(*it, children.back());
+9 -9
View File
@@ -184,7 +184,7 @@ enum class ValStatus {
/**
* Multiple validations by a validator for different ledgers
*/
Conflicting
Conflicting,
};
inline std::string
@@ -366,7 +366,7 @@ private:
removeTrie(std::scoped_lock<Mutex> const&, NodeID const& nodeID, Validation const& val)
{
{
auto it = acquiring_.find(std::make_pair(val.seq(), val.ledgerID()));
auto const it = acquiring_.find(std::make_pair(val.seq(), val.ledgerID()));
if (it != acquiring_.end())
{
it->second.erase(nodeID);
@@ -375,7 +375,7 @@ private:
}
}
{
auto it = lastLedger_.find(nodeID);
auto const it = lastLedger_.find(nodeID);
if (it != lastLedger_.end() && it->second.id() == val.ledgerID())
{
trie_.remove(it->second);
@@ -443,7 +443,7 @@ private:
// Clear any prior acquiring ledger for this node
if (prior)
{
auto it = acquiring_.find(*prior);
auto const it = acquiring_.find(*prior);
if (it != acquiring_.end())
{
it->second.erase(nodeID);
@@ -455,7 +455,7 @@ private:
checkAcquired(lock);
std::pair<Seq, ID> const valPair{val.seq(), val.ledgerID()};
auto it = acquiring_.find(valPair);
auto const it = acquiring_.find(valPair);
if (it != acquiring_.end())
{
it->second.insert(nodeID);
@@ -529,7 +529,7 @@ private:
}
else
{
auto cit = typename decltype(current_)::const_iterator{it};
auto const cit = typename decltype(current_)::const_iterator{it};
// contains a live record
f(cit->first, cit->second);
++it;
@@ -554,7 +554,7 @@ private:
void
byLedger(std::scoped_lock<Mutex> const&, ID const& ledgerID, Pre&& pre, F&& f)
{
auto it = byLedger_.find(ledgerID);
auto const it = byLedger_.find(ledgerID);
if (it != byLedger_.end())
{
// Update set time since it is being used
@@ -857,7 +857,7 @@ public:
if (!preferred)
{
// fall back to majority over acquiring ledgers
auto it = std::ranges::max_element(acquiring_, [](auto const& a, auto const& b) {
auto const it = std::ranges::max_element(acquiring_, [](auto const& a, auto const& b) {
std::pair<Seq, ID> const& aKey = a.first;
typename HashSet<NodeID>::size_type const& aSize = a.second.size();
std::pair<Seq, ID> const& bKey = b.first;
@@ -937,7 +937,7 @@ public:
return (preferred->first >= minSeq) ? preferred->second : lcl.id();
// Otherwise, rely on peer ledgers
auto it = std::ranges::max_element(peerCounts, [](auto const& a, auto const& b) {
auto const it = std::ranges::max_element(peerCounts, [](auto const& a, auto const& b) {
// Prefer larger counts, then larger ids on ties
// (max_element expects this to return true if a < b)
return std::tie(a.second, a.first) < std::tie(b.second, b.first);
+1 -1
View File
@@ -156,7 +156,7 @@ public:
{
if (!allClosuresDoneCond_.wait_for(lock, wait, [this] { return closureCount_ == 0; }))
{
if (auto stream = j.error())
if (auto const stream = j.error())
stream << name << " waiting for ClosureCounter::join().";
allClosuresDoneCond_.wait(lock, [this] { return closureCount_ == 0; });
}
+2 -2
View File
@@ -37,7 +37,7 @@ enum class HashRouterFlags : std::uint16_t {
PRIVATE6 = 0x2000,
// Used in apply.cpp
PRIVATE7 = 0x4000,
PRIVATE8 = 0x8000
PRIVATE8 = 0x8000,
};
constexpr HashRouterFlags
@@ -221,7 +221,7 @@ public:
/**
* Add a suppression peer and get message's relay status.
* Return pair:
* element 1: true if the key is added.
* element 1: true if the peer is added.
* element 2: optional is seated to the relay time point or
* is unseated if has not relayed yet.
*/
+6 -6
View File
@@ -30,12 +30,12 @@ private:
using namespace std::chrono_literals;
int const maxLimit = std::numeric_limits<int>::max();
auto add = [this](
JobType jt,
std::string name,
int limit,
std::chrono::milliseconds avgLatency,
std::chrono::milliseconds peakLatency) {
auto const add = [this](
JobType jt,
std::string name,
int limit,
std::chrono::milliseconds avgLatency,
std::chrono::milliseconds peakLatency) {
XRPL_ASSERT(!map.contains(jt), "xrpl::JobTypes::JobTypes::add : unique job type input");
[[maybe_unused]] auto const inserted =
+4 -3
View File
@@ -177,12 +177,13 @@ measureDurationAndLog(
std::chrono::duration<Rep, Period> maxDelay,
beast::Journal const& journal)
{
auto startTime = std::chrono::high_resolution_clock::now();
auto const startTime = std::chrono::high_resolution_clock::now();
auto result = func();
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
auto const endTime = std::chrono::high_resolution_clock::now();
auto const duration =
std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
if (duration > maxDelay)
{
JLOG(journal.warn()) << actionDescription << " took " << duration.count() << " ms";
+1 -1
View File
@@ -95,7 +95,7 @@ private:
ArraySeparator,
MemberSeparator,
Comment,
Error
Error,
};
class Token
+38 -1
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>
/**
@@ -26,7 +28,7 @@ enum class ValueType {
String, ///< UTF-8 string value
Boolean, ///< bool value
Array, ///< array value (ordered list)
Object ///< object value (collection of name/value pairs).
Object, ///< object value (collection of name/value pairs).
};
/**
@@ -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&);
+1 -1
View File
@@ -165,7 +165,7 @@ public:
beast::Journal j)
{
// Ask implementation what to do
auto actions = doVoting(
auto const actions = doVoting(
lastClosedLedger->rules(),
lastClosedLedger->parentCloseTime(),
getEnabledAmendments(*lastClosedLedger),
+1 -1
View File
@@ -50,7 +50,7 @@ enum ApplyFlags : std::uint32_t {
// afterward, so signature-presence checks (e.g. Batch signer matching)
// are skipped at proposal-creation time (On-Chain Cosigner spec
// §5.3.1.2).
TapProposal = 0x2000
TapProposal = 0x2000,
};
constexpr ApplyFlags
+2 -1
View File
@@ -20,7 +20,8 @@ constexpr std::chrono::seconds kLedgerPossibleTimeResolutions[] = {
std::chrono::seconds{30},
std::chrono::seconds{60},
std::chrono::seconds{90},
std::chrono::seconds{120}};
std::chrono::seconds{120},
};
/**
* Initial resolution of ledger close time.
+2 -2
View File
@@ -35,7 +35,7 @@ public:
{
std::scoped_lock const lock(mutex_);
auto it = map_.find(seq);
auto const it = map_.find(seq);
if ((it == map_.end()) || it->second)
{
@@ -88,7 +88,7 @@ public:
std::unique_lock<std::mutex> lock(mutex_);
do
{
auto it = map_.find(seq);
auto const it = map_.find(seq);
if (it == map_.end())
{
+3 -3
View File
@@ -164,7 +164,7 @@ getCandidateLedger(LedgerIndex requested)
areCompatible(
ReadView const& validLedger,
ReadView const& testLedger,
beast::Journal::Stream& s,
beast::Journal::Stream const& s,
char const* reason);
[[nodiscard]] bool
@@ -172,7 +172,7 @@ areCompatible(
UInt256 const& validHash,
LedgerIndex validIndex,
ReadView const& testLedger,
beast::Journal::Stream& s,
beast::Journal::Stream const& s,
char const* reason);
//------------------------------------------------------------------------------
@@ -185,7 +185,7 @@ areCompatible(
dirLink(
ApplyView& view,
AccountID const& owner,
SLE::pointer& object,
SLE::pointer const& object,
SF_UINT64 const& node = sfOwnerNode);
/**
+8 -4
View File
@@ -30,13 +30,17 @@ public:
static constexpr size_t kInitialBufferSize = kilobytes(256);
RawStateTable()
: monotonicResource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
kInitialBufferSize)}
: monotonicResource_{
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
kInitialBufferSize),
}
, items_{monotonicResource_.get()} {};
RawStateTable(RawStateTable const& rhs)
: monotonicResource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
kInitialBufferSize)}
: monotonicResource_{
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
kInitialBufferSize),
}
, items_{rhs.items_, monotonicResource_.get()}
, dropsDestroyed_{rhs.dropsDestroyed_} {};
+5 -5
View File
@@ -224,7 +224,7 @@ getAMMOfferStartWithTakerGets(
if (nTakerGetsConstraint < *nTakerGets)
nTakerGets = nTakerGetsConstraint;
auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
auto const getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerGets is integral.
auto const takerGets =
@@ -292,7 +292,7 @@ getAMMOfferStartWithTakerPays(
if (nTakerPaysConstraint < *nTakerPays)
nTakerPays = nTakerPaysConstraint;
auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
auto const getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerPays is integral.
auto const takerPays =
@@ -870,8 +870,8 @@ deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::
*/
void
initializeFeeAuctionVote(
ApplyView& view,
SLE::pointer& ammSle,
ApplyView const& view,
SLE::pointer const& ammSle,
AccountID const& account,
Asset const& lptAsset,
std::uint16_t tfee);
@@ -893,7 +893,7 @@ std::expected<bool, TER>
verifyAndAdjustLPTokenBalance(
Sandbox& sb,
STAmount const& lpTokens,
SLE::pointer& ammSle,
SLE::pointer const& ammSle,
AccountID const& account);
} // namespace xrpl
+1 -1
View File
@@ -20,7 +20,7 @@ enum class NodeObjectType : std::uint32_t {
Ledger = 1,
AccountNode = 3,
TransactionNode = 4,
Dummy = 512 // an invalid or missing object
Dummy = 512, // an invalid or missing object
};
/**
+1 -1
View File
@@ -28,7 +28,7 @@ enum class Status {
Unknown = 3,
BackendError = 4,
CustomCode = 100
CustomCode = 100,
};
/**
+2 -2
View File
@@ -429,7 +429,7 @@ Livecache<Allocator>::insert(Endpoint const& ep)
XRPL_ASSERT(
ep.hops <= (tuning::kMaxHops + 1),
"xrpl::peer_finder::Livecache::insert : maximum input hops");
auto result = cache_.emplace(ep.address, ep);
auto const result = cache_.emplace(ep.address, ep);
Element& e(result.first->second);
if (result.second)
{
@@ -497,7 +497,7 @@ Livecache<Allocator>::HopsT::shuffle()
std::ranges::copy(list, std::back_inserter(v));
std::shuffle(v.begin(), v.end(), defaultPrng());
list.clear();
for (auto& e : v)
for (auto const& e : v)
list.push_back(e);
}
}
+4 -4
View File
@@ -194,7 +194,7 @@ public:
"Port not specified for address:" + remoteAddress.toString());
}
auto result(fixed_.emplace(
auto const result(fixed_.emplace(
std::piecewise_construct,
std::forward_as_tuple(remoteAddress),
std::make_tuple(std::ref(clock))));
@@ -439,7 +439,7 @@ public:
// Mark fixed slot success
if (slot->fixed() && !slot->inbound())
{
auto iter(fixed_.find(slot->remoteEndpoint()));
auto const iter(fixed_.find(slot->remoteEndpoint()));
if (iter == fixed_.end())
{
logicError(
@@ -485,7 +485,7 @@ public:
// Count how many more outbound attempts to make
//
auto needed(counts_.attemptsNeeded());
auto const needed(counts_.attemptsNeeded());
if (needed == 0)
return none;
@@ -903,7 +903,7 @@ public:
// Mark fixed slot failure
if (slot->fixed() && !slot->inbound() && slot->state() != Slot::State::Active)
{
auto iter(fixed_.find(slot->remoteEndpoint()));
auto const iter(fixed_.find(slot->remoteEndpoint()));
if (iter == fixed_.end())
{
logicError(
+46 -17
View File
@@ -6,31 +6,25 @@
#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. A WebSocket message is
* one flat object and names it at its top level at every version. A JSON-RPC
* request names it in its parameters, or at its own top level: an entry of a
* `"method": "batch"` body at every version, a lone request from
* kApiMinimumSpecVersion up.
*/
namespace rpc {
@@ -46,6 +40,31 @@ static constexpr auto kApiCommandLineVersion = kApiVersion<1>; // TODO Bump to
static constexpr auto kApiBetaVersion = kApiVersion<3>;
static constexpr auto kApiMaximumValidVersion = kApiBetaVersion;
/**
* First API version that follows the JSON-RPC 2.0 specification.
*
* From this version up a lone JSON-RPC request may state its `api_version` at
* its own top level, beside the members the specification defines there, as a
* WebSocket message always has. The member is an XRPL extension: the
* specification defines no version member.
*
* Distinct from kApiBetaVersion, the same number today, which says instead that
* the version needs [beta_rpc_api] to be reachable.
*/
static constexpr auto kApiMinimumSpecVersion = kApiVersion<3>;
/**
* Whether @p apiVersion follows the JSON-RPC 2.0 specification.
*
* @param apiVersion The version a request asked for.
* @return Whether that version follows the specification.
*/
[[nodiscard]] constexpr bool
isSpecVersion(unsigned apiVersion)
{
return apiVersion >= kApiMinimumSpecVersion;
}
static_assert(kApiInvalidVersion < kApiMinimumSupportedVersion);
static_assert(
kApiVersionIfUnspecified >= kApiMinimumSupportedVersion &&
@@ -57,6 +76,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)
{
+65 -6
View File
@@ -3,6 +3,7 @@
#include <xrpl/json/json_value.h>
#include <string>
#include <string_view>
namespace xrpl {
@@ -144,7 +145,58 @@ 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 + ledger_entry
RpcMalformedRequest = 107,
// ledger_accept
RpcNotStandAlone = 108,
// ledger_entry, API version 1 only
RpcUnknownOption = 109,
// ledger_entry field validation, one code per malformed field. The ledger_entry helpers report
// invalidParams (31) for all of them, so each token needs a row of its own. Alphabetical by
// token here only: the enum is append-only once a code ships.
//
// `malformedIssue` duplicates `issueMalformed` (93): same message, same status, two tokens,
// both on the wire already. Do not add a third spelling.
RpcMalformedAccount = 110,
RpcMalformedAddress = 111,
RpcMalformedAuthorized = 112,
RpcMalformedAuthorizedCredentials = 113,
RpcMalformedBridgeAccount = 114,
RpcMalformedBroker = 115,
RpcMalformedCurrency = 116,
RpcMalformedDirRoot = 117,
RpcMalformedDocumentID = 118,
RpcMalformedIssue = 119,
RpcMalformedIssuingChainDoor = 120,
RpcMalformedLockingChainDoor = 121,
RpcMalformedMPTIssuanceID = 122,
RpcMalformedMPTokenIssuance = 123,
RpcMalformedOwner = 124,
RpcMalformedSeq = 125,
RpcMalformedSponsee = 126,
RpcMalformedSponsor = 127,
RpcMalformedXChainOwnedClaimID = 128,
RpcMalformedXChainOwnedCreateAccountClaimID = 129,
// subscribe
RpcApiVersionConflict = 130,
// RpcLast should always equal the last code.
RpcLast = RpcApiVersionConflict,
};
/**
@@ -180,11 +232,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)
{
@@ -202,6 +249,18 @@ struct ErrorInfo
ErrorInfo const&
getErrorInfo(ErrorCodeI code);
/**
* Returns the error code that owns @p token.
*
* A linear scan over views measured at compile time, run once per error reply.
* A duplicate token is a build error, so the answer is never ambiguous.
*
* @param token The error token to resolve.
* @return The code the table gives @p token, or RpcUnknown if no row names it.
*/
ErrorCodeI
codeForToken(std::string_view token);
/**
* Add or update the json update to reflect the error code.
*/
+45
View File
@@ -0,0 +1,45 @@
#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
* XRPL token and code travel in the error's `data` member, so renumbering an
* XRPL error cannot change a JSON-RPC code.
*
* 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
View File
@@ -40,8 +40,6 @@ struct LedgerHeader
// If validated is false, it means "not yet validated."
// Once validated is true, it will never be set false at a later time.
// NOTE: If you are accessing this directly, you are probably doing it
// wrong. Use LedgerMaster::isValidated().
// VFALCO TODO Make this not mutable
bool mutable validated = false;
bool accepted = false;
+2
View File
@@ -26,6 +26,7 @@ class STTx;
* greater than the maximum value of uint16.
*/
// Macro-generated, complex
// NOLINTBEGIN(readability-trailing-comma) the macro supplies the trailing comma
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum GranularPermissionType : std::uint32_t {
#pragma push_macro("GRANULAR_PERMISSION")
@@ -38,6 +39,7 @@ enum GranularPermissionType : std::uint32_t {
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
};
// NOLINTEND(readability-trailing-comma)
class Permission
{
+4 -2
View File
@@ -284,8 +284,10 @@ public:
auto const [minV, maxV] = std::minmax(q1.value_, q2.value_);
auto mantissa = [](std::uint64_t rate) { return rate & ~(255ull << (64 - 8)); };
auto exponent = [](std::uint64_t rate) { return static_cast<int>(rate >> (64 - 8)) - 100; };
auto const mantissa = [](std::uint64_t rate) { return rate & ~(255ull << (64 - 8)); };
auto const exponent = [](std::uint64_t rate) {
return static_cast<int>(rate >> (64 - 8)) - 100;
};
auto const minVMantissa = mantissa(minV);
auto const maxVMantissa = mantissa(maxV);
+1 -1
View File
@@ -29,7 +29,7 @@ struct JsonOptions
DisableApiPriorV2 = 0b0000'0010,
// IMPORTANT `All` must be union of all of the above; see also operator~
All = IncludeDate | DisableApiPriorV2 // 0b0000'0011
All = IncludeDate | DisableApiPriorV2, // 0b0000'0011
};
constexpr JsonOptions(UnderlyingT v) noexcept : value(v)
+1 -1
View File
@@ -440,7 +440,7 @@ private:
// These values are carefully chosen to do the right thing if passed
// to SField::shouldInclude (bool)
OmitSigningFields = false,
WithAllFields = true
WithAllFields = true,
};
void
+1 -1
View File
@@ -36,7 +36,7 @@ enum class TxnSql : char {
Held = 'H',
Validated = 'V',
Included = 'I',
Unknown = 'U'
Unknown = 'U',
};
class STTx final : public STObject, public CountedObject<STTx>
+1 -1
View File
@@ -250,7 +250,7 @@ public:
bool
getBitString(BaseUInt<Bits, Tag>& data, int offset) const
{
auto success = (offset + (Bits / 8)) <= data_.size();
auto const success = (offset + (Bits / 8)) <= data_.size();
if (success)
memcpy(data.begin(), &data_.front() + offset, (Bits / 8));
return success;
+1 -1
View File
@@ -32,7 +32,7 @@ enum class SignatureRole {
/**
* The sponsor's signature, in sfSponsorSignature.
*/
Sponsor
Sponsor,
};
/**
+2 -2
View File
@@ -51,7 +51,7 @@ enum TELcodes : TERUnderlyingType {
telWRONG_NETWORK,
telREQUIRES_NETWORK_ID,
telNETWORK_ID_MAKES_TX_NON_CANONICAL,
telENV_RPC_FAILED
telENV_RPC_FAILED,
};
//------------------------------------------------------------------------------
@@ -247,7 +247,7 @@ enum TEScodes : TERUnderlyingType {
// Implications:
// - Applied
// - Forwarded
tesSUCCESS = 0
tesSUCCESS = 0,
};
//------------------------------------------------------------------------------
+3 -1
View File
@@ -319,7 +319,9 @@ inline FlagMap const&
getUniversalFlags()
{
static FlagMap const flags = {
{"tfFullyCanonicalSig", tfFullyCanonicalSig}, {"tfInnerBatchTxn", tfInnerBatchTxn}};
{"tfFullyCanonicalSig", tfFullyCanonicalSig},
{"tfInnerBatchTxn", tfInnerBatchTxn},
};
return flags;
}
+1 -1
View File
@@ -427,7 +427,7 @@ mulDivU(Source1 value, Dest mul, Source2 div)
static_cast<std::uint64_t>(value.value()),
static_cast<std::uint64_t>(mul.value()));
auto quotient = product / div.value();
auto const quotient = product / div.value();
if (quotient > kMax)
return std::nullopt;
+1 -1
View File
@@ -236,7 +236,7 @@ enum class AttestationMatch {
// all of the fields match, except the dst field
MatchExceptDst,
// all of the fields match
Match
Match,
};
struct XChainClaimAttestation
+3 -3
View File
@@ -119,13 +119,13 @@ inplaceBigintDivRem(std::span<uint64_t> numerator, std::uint64_t divisor)
// LCOV_EXCL_STOP
}
auto toU128 = [](std::uint64_t high, std::uint64_t low) -> unsigned __int128 {
auto const toU128 = [](std::uint64_t high, std::uint64_t low) -> unsigned __int128 {
unsigned __int128 const high128 = high;
unsigned __int128 const low128 = low;
return ((high128 << 64) | low128);
};
auto divRe64 = [](unsigned __int128 num,
std::uint64_t denom) -> std::tuple<std::uint64_t, std::uint64_t> {
auto const divRe64 = [](unsigned __int128 num,
std::uint64_t denom) -> std::tuple<std::uint64_t, std::uint64_t> {
unsigned __int128 const denom128 = denom;
unsigned __int128 const d = num / denom128;
unsigned __int128 const r = num - (denom128 * d);
+2 -2
View File
@@ -245,7 +245,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)
@@ -559,7 +559,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
+1 -1
View File
@@ -24,7 +24,7 @@ enum class TokenType : std::uint8_t {
AccountPublic = 35,
AccountSecret = 34,
FamilyGenerator = 41, // unused
FamilySeed = 33
FamilySeed = 33,
};
template <class T>
+37 -28
View File
@@ -46,9 +46,10 @@ inline constexpr std::uint32_t kSqliteTuningCutoff = 10'000'000;
inline constexpr auto kLgrDbName{"ledger.db"};
inline constexpr std::array<char const*, 5> kLgrDbInit{
{"BEGIN TRANSACTION;",
{
"BEGIN TRANSACTION;",
"CREATE TABLE IF NOT EXISTS Ledgers ( \
"CREATE TABLE IF NOT EXISTS Ledgers ( \
LedgerHash CHARACTER(64) PRIMARY KEY, \
LedgerSeq BIGINT UNSIGNED, \
PrevHash CHARACTER(64), \
@@ -60,12 +61,14 @@ inline constexpr std::array<char const*, 5> kLgrDbInit{
AccountSetHash CHARACTER(64), \
TransSetHash CHARACTER(64) \
);",
"CREATE INDEX IF NOT EXISTS SeqLedger ON Ledgers(LedgerSeq);",
"CREATE INDEX IF NOT EXISTS SeqLedger ON Ledgers(LedgerSeq);",
// Old table and indexes no longer needed
"DROP TABLE IF EXISTS Validations;",
// Old table and indexes no longer needed
"DROP TABLE IF EXISTS Validations;",
"END TRANSACTION;"}};
"END TRANSACTION;",
},
};
////////////////////////////////////////////////////////////////////////////////
@@ -73,9 +76,10 @@ inline constexpr std::array<char const*, 5> kLgrDbInit{
inline constexpr auto kTxDbName{"transaction.db"};
inline constexpr std::array<char const*, 8> kTxDbInit{
{"BEGIN TRANSACTION;",
{
"BEGIN TRANSACTION;",
"CREATE TABLE IF NOT EXISTS Transactions ( \
"CREATE TABLE IF NOT EXISTS Transactions ( \
TransID CHARACTER(64) PRIMARY KEY, \
TransType CHARACTER(24), \
FromAcct CHARACTER(35), \
@@ -85,57 +89,62 @@ inline constexpr std::array<char const*, 8> kTxDbInit{
RawTxn BLOB, \
TxnMeta BLOB \
);",
"CREATE INDEX IF NOT EXISTS TxLgrIndex ON \
"CREATE INDEX IF NOT EXISTS TxLgrIndex ON \
Transactions(LedgerSeq);",
"CREATE TABLE IF NOT EXISTS AccountTransactions ( \
"CREATE TABLE IF NOT EXISTS AccountTransactions ( \
TransID CHARACTER(64), \
Account CHARACTER(64), \
LedgerSeq BIGINT UNSIGNED, \
TxnSeq INTEGER \
);",
"CREATE INDEX IF NOT EXISTS AcctTxIDIndex ON \
"CREATE INDEX IF NOT EXISTS AcctTxIDIndex ON \
AccountTransactions(TransID);",
"CREATE INDEX IF NOT EXISTS AcctTxIndex ON \
"CREATE INDEX IF NOT EXISTS AcctTxIndex ON \
AccountTransactions(Account, LedgerSeq, TxnSeq, TransID);",
"CREATE INDEX IF NOT EXISTS AcctLgrIndex ON \
"CREATE INDEX IF NOT EXISTS AcctLgrIndex ON \
AccountTransactions(LedgerSeq, Account, TransID);",
"END TRANSACTION;"}};
"END TRANSACTION;",
},
};
////////////////////////////////////////////////////////////////////////////////
inline constexpr auto kWalletDbName{"wallet.db"};
inline constexpr std::array<char const*, 6> kWalletDbInit{
{"BEGIN TRANSACTION;",
{
"BEGIN TRANSACTION;",
// A node's identity must be persisted, including
// for clustering purposes. This table holds one
// entry: the server's unique identity, but the
// value can be overriden by specifying a node
// identity in the config file using a [node_seed]
// entry.
"CREATE TABLE IF NOT EXISTS NodeIdentity ( \
// A node's identity must be persisted, including
// for clustering purposes. This table holds one
// entry: the server's unique identity, but the
// value can be overriden by specifying a node
// identity in the config file using a [node_seed]
// entry.
"CREATE TABLE IF NOT EXISTS NodeIdentity ( \
PublicKey CHARACTER(53), \
PrivateKey CHARACTER(52) \
);",
// Peer reservations
"CREATE TABLE IF NOT EXISTS PeerReservations ( \
// Peer reservations
"CREATE TABLE IF NOT EXISTS PeerReservations ( \
PublicKey CHARACTER(53) UNIQUE NOT NULL, \
Description CHARACTER(64) NOT NULL \
);",
// Validator Manifests
"CREATE TABLE IF NOT EXISTS ValidatorManifests ( \
// Validator Manifests
"CREATE TABLE IF NOT EXISTS ValidatorManifests ( \
RawData BLOB NOT NULL \
);",
"CREATE TABLE IF NOT EXISTS PublisherManifests ( \
"CREATE TABLE IF NOT EXISTS PublisherManifests ( \
RawData BLOB NOT NULL \
);",
"END TRANSACTION;"}};
"END TRANSACTION;",
},
};
} // namespace xrpl
+2 -2
View File
@@ -50,8 +50,8 @@ struct LedgerRange
enum class DelegateType {
Actor, ///< Another account signed and submitted transactions on behalf of this account (this
///< account is the owner/delegator).
Authorizer ///< This account signed and submitted transactions on behalf of another account
///< (this account is the signer/delegatee).
Authorizer, ///< This account signed and submitted transactions on behalf of another account
///< (this account is the signer/delegatee).
};
struct DelegateFilter
+1 -1
View File
@@ -21,7 +21,7 @@ enum class Disposition {
* Consumer should be disconnected for excess consumption.
*/
,
Drop
Drop,
};
} // namespace xrpl::resource
+3 -2
View File
@@ -334,7 +334,7 @@ public:
if (iter->whenExpires <= elapsed)
{
JLOG(journal_.debug()) << "Expired " << *iter;
auto tableIter = table_.find(*iter->key);
auto const tableIter = table_.find(*iter->key);
++iter;
erase(tableIter);
}
@@ -437,7 +437,8 @@ public:
static_assert(
kFeeLogAsWarn > kFeeLogAsInfo && kFeeLogAsInfo > kFeeLogAsDebug && kFeeLogAsDebug > 10);
static auto kGetStream = [](resource::Charge::value_type cost, beast::Journal& journal) {
static auto const kGetStream = [](resource::Charge::value_type cost,
beast::Journal& journal) {
if (cost >= kFeeLogAsWarn)
return journal.warn();
if (cost >= kFeeLogAsInfo)
+62 -23
View File
@@ -17,6 +17,7 @@
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
namespace xrpl {
@@ -113,7 +114,11 @@ public:
// you get transactions as they occur or once their
// results are confirmed
virtual void
subAccount(Ref ispListener, HashSet<AccountID> const& vnaAccountIDs, bool realTime) = 0;
subAccount(
Ref ispListener,
HashSet<AccountID> const& vnaAccountIDs,
bool realTime,
unsigned int apiVersion) = 0;
// for normal use, removes from InfoSub and server
virtual void
@@ -130,10 +135,12 @@ public:
/**
* subscribe an account's new transactions and retrieve the account's
* historical transactions
* @param apiVersion The version the subscription is registered at,
* which is the version its messages are shaped for.
* @return rpcSUCCESS if successful, otherwise an error code
*/
virtual ErrorCodeI
subAccountHistory(Ref ispListener, AccountID const& account) = 0;
subAccountHistory(Ref ispListener, AccountID const& account, unsigned int apiVersion) = 0;
/**
* unsubscribe an account's transactions
@@ -196,30 +203,34 @@ public:
HashSet<AccountID> historyAccounts) = 0;
// VFALCO TODO Document the bool return value
//
// Every sub* below takes the version the subscription is registered at, which is the
// version its messages are shaped for. Registering the same subscription again replaces
// the entry, so the newest registration decides that subscription's version.
virtual bool
subLedger(Ref ispListener, json::Value& jvResult) = 0;
subLedger(Ref ispListener, json::Value& jvResult, unsigned int apiVersion) = 0;
virtual bool
unsubLedger(std::uint64_t uListener) = 0;
virtual bool
subBookChanges(Ref ispListener) = 0;
subBookChanges(Ref ispListener, unsigned int apiVersion) = 0;
virtual bool
unsubBookChanges(std::uint64_t uListener) = 0;
virtual bool
subManifests(Ref ispListener) = 0;
subManifests(Ref ispListener, unsigned int apiVersion) = 0;
virtual bool
unsubManifests(std::uint64_t uListener) = 0;
virtual void
pubManifest(Manifest const&) = 0;
virtual bool
subServer(Ref ispListener, json::Value& jvResult, bool admin) = 0;
subServer(Ref ispListener, json::Value& jvResult, bool admin, unsigned int apiVersion) = 0;
virtual bool
unsubServer(std::uint64_t uListener) = 0;
virtual bool
subBook(Ref ispListener, Book const&) = 0;
subBook(Ref ispListener, Book const&, unsigned int apiVersion) = 0;
/**
* Remove a book subscription for a live subscriber.
@@ -248,7 +259,8 @@ public:
* InfoSub::bookSubscriptions_ because the InfoSub is being destroyed.
* Called by ~InfoSub() for each book in bookSubscriptions_.
*
* @param uListener The sequence number of the subscriber being torn down.
* @param uListener The sequence number of the subscriber being torn
* down.
* @param book The order book entry to remove.
* @return true if the entry was present and removed, false otherwise
* (e.g., already removed by a concurrent RPC unsubscribe).
@@ -259,22 +271,22 @@ public:
unsubBookInternal(std::uint64_t uListener, Book const&) = 0;
virtual bool
subTransactions(Ref ispListener) = 0;
subTransactions(Ref ispListener, unsigned int apiVersion) = 0;
virtual bool
unsubTransactions(std::uint64_t uListener) = 0;
virtual bool
subRTTransactions(Ref ispListener) = 0;
subRTTransactions(Ref ispListener, unsigned int apiVersion) = 0;
virtual bool
unsubRTTransactions(std::uint64_t uListener) = 0;
virtual bool
subValidations(Ref ispListener) = 0;
subValidations(Ref ispListener, unsigned int apiVersion) = 0;
virtual bool
unsubValidations(std::uint64_t uListener) = 0;
virtual bool
subPeerStatus(Ref ispListener) = 0;
subPeerStatus(Ref ispListener, unsigned int apiVersion) = 0;
virtual bool
unsubPeerStatus(std::uint64_t uListener) = 0;
@@ -282,12 +294,12 @@ public:
pubPeerStatus(std::function<json::Value()> const&) = 0;
virtual bool
subConsensus(Ref ispListener) = 0;
subConsensus(Ref ispListener, unsigned int apiVersion) = 0;
virtual bool
unsubConsensus(std::uint64_t uListener) = 0;
virtual void
subMPT(InfoSub::Ref ispListener, HashSet<MPTID> const& mptIDs) = 0;
subMPT(InfoSub::Ref ispListener, HashSet<MPTID> const& mptIDs, unsigned int apiVersion) = 0;
virtual void
unsubMPT(InfoSub::Ref ispListener, HashSet<MPTID> const& mptIDs) = 0;
@@ -385,8 +397,9 @@ public:
/**
* Whether this connection already tracks an account-history for @p account.
*
* `doSubscribe` reads this to charge the cap for an account_history_tx_stream
* only when it is net-new, matching the account branches.
* `doSubscribe` reads this to charge the cap for an
* account_history_tx_stream only when it is net-new, matching the account
* branches.
*
* @param account The account an account_history_tx_stream would add.
* @return true if @p account is already in the account-history set.
@@ -447,11 +460,26 @@ public:
std::shared_ptr<InfoSubRequest> const&
getRequest();
void
setApiVersion(unsigned int apiVersion);
[[nodiscard]] unsigned int
getApiVersion() const noexcept;
/**
* Establishes @p apiVersion as the version every subscription on this
* connection is served at.
*
* A connection serves one version: one message has one shape, and several
* of its subscriptions may match it. The first `subscribe` decides it and
* it is never cleared. A `subscribe` refused for another reason still
* establishes it, the registrations it makes being spread through the
* handler.
*
* For the subscribe path alone. Nothing reads this value back: a publisher
* reads the version recorded on the subscription it took the sink from.
*
* @param apiVersion The version the `subscribe` named.
* @return nullopt when @p apiVersion is this connection's version;
* otherwise the version already established.
* @note Thread-safe: takes `lock_`.
*/
[[nodiscard]] std::optional<unsigned int>
establishSubscriptionVersion(unsigned int apiVersion);
void
insertSubMPTInfo(MPTID const& mptID);
@@ -485,9 +513,20 @@ private:
HashSet<AccountID> accountHistorySubscriptions_;
HashSet<Book> bookSubscriptions_;
HashSet<MPTID> mptSubscriptions_;
unsigned int apiVersion_ = 0;
// The API version every subscription on this connection was registered at, set by the first
// `subscribe` and never cleared. Guarded by lock_. Read only by establishSubscriptionVersion,
// so no publisher can reach it.
std::optional<unsigned int> subscriptionApiVersion_;
static int
/**
* The next sequence number, which is what identifies a connection.
*
* 64 bits wide, as the counter and `seq_` are, so no two live connections
* share an identity.
*
* @return A sequence number no live connection holds.
*/
static std::uint64_t
assignId()
{
static std::atomic<std::uint64_t> kID(0);
+3 -3
View File
@@ -328,7 +328,7 @@ enum class ManifestDisposition {
Invalid, ///< Timely, but invalid signature
UntrustedCapacity ///< Unlisted and limit reached
UntrustedCapacity, ///< Unlisted and limit reached
};
inline std::string
@@ -361,8 +361,8 @@ to_string(ManifestDisposition m)
* configured keys should use `Uncapped`.
*/
enum class ManifestRateLimitCapPolicy : std::uint8_t {
Capped, ///< Subject to the untrusted cap (unlisted peer gossip)
Uncapped ///< Bypasses the cap (listed/trusted or config manifests)
Capped, ///< Subject to the untrusted cap (unlisted peer gossip)
Uncapped, ///< Bypasses the cap (listed/trusted or config manifests)
};
class DatabaseCon;
+2 -2
View File
@@ -62,7 +62,7 @@ enum class OperatingMode {
CONNECTED = 1, ///< convinced we are talking to the network
SYNCING = 2, ///< fallen slightly behind
TRACKING = 3, ///< convinced we agree with the network
FULL = 4 ///< we have the ledger and can even validate
FULL = 4, ///< we have the ledger and can even validate
};
/**
@@ -196,7 +196,7 @@ public:
virtual bool
isFull() = 0;
virtual void
setMode(OperatingMode om, char const* reason) = 0;
setMode(OperatingMode om) = 0;
virtual bool
isBlocked() = 0;
virtual bool
+1 -1
View File
@@ -267,7 +267,7 @@ template <class Handler, class Impl>
void
BaseHTTPPeer<Handler, Impl>::onTimer()
{
auto ec = boost::system::errc::make_error_code(boost::system::errc::timed_out);
auto const ec = boost::system::errc::make_error_code(boost::system::errc::timed_out);
fail(ec, "timer");
}
+9 -7
View File
@@ -96,10 +96,12 @@ private:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
bool ssl_{
port_.protocol.contains("https") || port_.protocol.contains("wss") ||
port_.protocol.contains("wss2") || port_.protocol.contains("peer")};
port_.protocol.contains("wss2") || port_.protocol.contains("peer"),
};
bool plain_{
port_.protocol.contains("http") || port_.protocol.contains("ws") ||
port_.protocol.contains("ws2")};
port_.protocol.contains("ws2"),
};
static constexpr std::chrono::milliseconds kInitialAcceptDelay{50};
static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000};
std::chrono::milliseconds acceptDelay_{kInitialAcceptDelay};
@@ -207,12 +209,12 @@ Door<Handler>::Detector::doDetect(boost::asio::yield_context doYield)
{
if (ssl)
{
if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
if (auto const sp = ios().template emplace<SSLHTTPPeer<Handler>>(
port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
sp->run();
return;
}
if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
if (auto const sp = ios().template emplace<PlainHTTPPeer<Handler>>(
port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
sp->run();
return;
@@ -328,12 +330,12 @@ Door<Handler>::create(
{
if (ssl)
{
if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
if (auto const sp = ios().template emplace<SSLHTTPPeer<Handler>>(
port_, handler_, ioc_, j_, remoteAddress, buffers, std::move(stream)))
sp->run();
return;
}
if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
if (auto const sp = ios().template emplace<PlainHTTPPeer<Handler>>(
port_, handler_, ioc_, j_, remoteAddress, buffers, std::move(stream)))
sp->run();
}
@@ -390,7 +392,7 @@ Door<Handler>::doAccept(boost::asio::yield_context doYield)
if (ssl_ && plain_)
{
if (auto sp = ios().template emplace<Detector>(
if (auto const sp = ios().template emplace<Detector>(
port_, handler_, ioc_, std::move(stream), remoteAddress, j_))
sp->run();
}
+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 -1
View File
@@ -163,7 +163,7 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
{
ports_.push_back(port);
auto& internalPort = ports_.back();
if (auto sp = ios_.emplace<Door<Handler>>(handler_, ioContext_, internalPort, j_))
if (auto const sp = ios_.emplace<Door<Handler>>(handler_, ioContext_, internalPort, j_))
{
list_.push_back(sp);
+2 -2
View File
@@ -225,14 +225,14 @@ IOList::close(Finisher&& f)
if (closed_)
return;
closed_ = true;
auto map = std::move(map_);
auto const map = std::move(map_);
if (!map.empty())
{
f_ = std::forward<Finisher>(f);
lock.unlock();
for (auto const& p : map)
{
if (auto sp = p.second.lock())
if (auto const sp = p.second.lock())
sp->close();
}
}
+3 -3
View File
@@ -486,7 +486,7 @@ private:
XRPL_ASSERT(node, "xrpl::SHAMap::NodePathStack::pushChild : non-null node input");
XRPL_ASSERT(
!stack_.empty(), "xrpl::SHAMap::NodePathStack::pushChild : non-empty stack");
auto childID = stack_.top().second.getChildNodeID(branch);
auto const childID = stack_.top().second.getChildNodeID(branch);
XRPL_ASSERT_IF(
node->isInner(),
childID.getDepth() < kLeafDepth,
@@ -495,7 +495,7 @@ private:
node->isLeaf(),
childID.isPrefixOf(leafKey(*node)),
"xrpl::SHAMap::NodePathStack::pushChild : leaf key below branch");
stack_.emplace(std::move(node), std::move(childID));
stack_.emplace(std::move(node), childID);
}
/**
@@ -878,7 +878,7 @@ SHAMap::ConstIterator::operator++()
inline SHAMap::ConstIterator
SHAMap::ConstIterator::operator++(int)
{
auto tmp = *this;
auto const tmp = *this;
++(*this);
return tmp;
}
+120 -31
View File
@@ -14,35 +14,112 @@ private:
public:
SHAMapAddNode();
/**
* Record one node that was rejected.
*/
void
incInvalid();
/**
* Record one node that produced a good result.
*/
void
incUseful();
/**
* Record one node that was not needed: the map already held it, or the map
* or its acquisition was no longer taking nodes. isGood() counts it on the
* accepted side.
*/
void
incDuplicate();
void
reset();
/**
* @return How many nodes produced a good result.
*/
[[nodiscard]] int
getGood() const;
/**
* @return How many nodes this code rejected, which is not how many the
* batch was given: a batch that stops at its first bad node counts
* one, and a batch that carries on counts each.
*/
[[nodiscard]] int
getBad() const;
/**
* @return How many nodes were not needed, whether already held or offered
* to a map no longer taking nodes, tallied apart from the good and
* bad counts.
*/
[[nodiscard]] int
getDuplicate() const;
/**
* Whether nodes that produced a good result or were not needed outnumber
* the ones rejected.
*
* @return Whether the batch was good.
*/
[[nodiscard]] bool
isGood() const;
/**
* @return Whether at least one node in the batch was rejected.
*/
[[nodiscard]] bool
isInvalid() const;
/**
* @return Whether at least one node in the batch produced a good result.
*/
[[nodiscard]] bool
isUseful() const;
/**
* @return A verdict recording one node that was not needed.
*/
static SHAMapAddNode
duplicate();
/**
* @return A verdict recording one useful node.
*/
static SHAMapAddNode
useful();
/**
* @return A verdict recording one invalid node.
*/
static SHAMapAddNode
invalid();
/**
* Clear every count back to zero.
*/
void
reset();
/**
* Render the tally as a log line.
*
* @return The tally, e.g. "good:2 bad:1 dupe:1", or "no nodes processed" if
* every count is zero.
*/
[[nodiscard]] std::string
get() const;
/**
* Add another verdict's counts into this one.
*
* @param n The verdict to add.
* @return This verdict, updated.
*/
SHAMapAddNode&
operator+=(SHAMapAddNode const& n);
static SHAMapAddNode
duplicate();
static SHAMapAddNode
useful();
static SHAMapAddNode
invalid();
private:
SHAMapAddNode(int good, int bad, int duplicate);
};
@@ -74,18 +151,30 @@ SHAMapAddNode::incDuplicate()
++duplicate_;
}
inline void
SHAMapAddNode::reset()
{
good_ = bad_ = duplicate_ = 0;
}
inline int
SHAMapAddNode::getGood() const
{
return good_;
}
inline int
SHAMapAddNode::getBad() const
{
return bad_;
}
inline int
SHAMapAddNode::getDuplicate() const
{
return duplicate_;
}
inline bool
SHAMapAddNode::isGood() const
{
return (good_ + duplicate_) > bad_;
}
inline bool
SHAMapAddNode::isInvalid() const
{
@@ -98,22 +187,6 @@ SHAMapAddNode::isUseful() const
return good_ > 0;
}
inline SHAMapAddNode&
SHAMapAddNode::operator+=(SHAMapAddNode const& n)
{
good_ += n.good_;
bad_ += n.bad_;
duplicate_ += n.duplicate_;
return *this;
}
inline bool
SHAMapAddNode::isGood() const
{
return (good_ + duplicate_) > bad_;
}
inline SHAMapAddNode
SHAMapAddNode::duplicate()
{
@@ -132,6 +205,12 @@ SHAMapAddNode::invalid()
return SHAMapAddNode(0, 1, 0);
}
inline void
SHAMapAddNode::reset()
{
good_ = bad_ = duplicate_ = 0;
}
inline std::string
SHAMapAddNode::get() const
{
@@ -160,4 +239,14 @@ SHAMapAddNode::get() const
return ret;
}
inline SHAMapAddNode&
SHAMapAddNode::operator+=(SHAMapAddNode const& n)
{
good_ += n.good_;
bad_ += n.bad_;
duplicate_ += n.duplicate_;
return *this;
}
} // namespace xrpl
+1 -1
View File
@@ -31,7 +31,7 @@ enum class SHAMapNodeType {
TnInner = 1,
TnTransactionNm = 2, // transaction, no metadata
TnTransactionMd = 3, // transaction, with metadata
TnAccountState = 4
TnAccountState = 4,
};
class SHAMapTreeNode : public IntrusiveRefCounts
+2 -2
View File
@@ -33,7 +33,7 @@ enum class Validity {
/**
* Signature and local checks are good / passed.
*/
Valid
Valid,
};
/**
@@ -133,7 +133,7 @@ enum class ApplyTransactionResult {
/**
* Should be retried in this ledger
*/
Retry
Retry,
};
/**
+1 -1
View File
@@ -65,7 +65,7 @@ public:
* Affects the ability of subsequent transactions
* to claim a fee. Eg. `SetRegularKey`
*/
Blocker
Blocker,
};
private:
+1
View File
@@ -19,6 +19,7 @@ setUnion(boost::container::flat_set<T>& dst, boost::container::flat_set<T> const
return;
dst.reserve(dst.size() + src.size());
// NOLINTNEXTLINE(readability-trailing-comma) fix-it drops comma after `{}`
dst.insert(boost::container::ordered_unique_range_t{}, src.begin(), src.end());
}
+10 -9
View File
@@ -102,7 +102,7 @@ struct FlowDebugInfo
[[nodiscard]] auto
duration(std::string const& tag) const
{
auto i = timePoints.find(tag);
auto const i = timePoints.find(tag);
if (i == timePoints.end())
{
// LCOV_EXCL_START
@@ -119,7 +119,7 @@ struct FlowDebugInfo
[[nodiscard]] std::size_t
count(std::string const& tag) const
{
auto i = counts.find(tag);
auto const i = counts.find(tag);
if (i == counts.end())
return 0;
return i->second;
@@ -151,7 +151,7 @@ struct FlowDebugInfo
void
inc(std::string const& tag)
{
auto i = counts.find(tag);
auto const i = counts.find(tag);
if (i == counts.end())
{
counts[tag] = 1;
@@ -212,24 +212,25 @@ struct FlowDebugInfo
};
auto writeXrpAmtList = [&writeList](
std::vector<EitherAmount> const& amts, char delim = ';') {
auto getVal = [](EitherAmount const& a) -> std::string {
auto const getVal = [](EitherAmount const& a) -> std::string {
return xrpl::to_string(a.get<XRPAmount>());
};
writeList(amts, getVal, delim);
};
auto writeIouAmtList = [&writeList](
std::vector<EitherAmount> const& amts, char delim = ';') {
auto getVal = [](EitherAmount const& a) -> std::string {
auto const getVal = [](EitherAmount const& a) -> std::string {
return xrpl::to_string(a.get<IOUAmount>());
};
writeList(amts, getVal, delim);
};
auto writeIntList = [&writeList](std::vector<size_t> const& vals, char delim = ';') {
auto const writeIntList = [&writeList](
std::vector<size_t> const& vals, char delim = ';') {
// NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter)
auto getVal = [](size_t const& v) -> size_t const& { return v; };
auto const getVal = [](size_t const& v) -> size_t const& { return v; };
writeList(vals, getVal);
};
auto writeNestedIouAmtList =
auto const writeNestedIouAmtList =
[&ostr, &writeIouAmtList](std::vector<std::vector<EitherAmount>> const& amts) {
ostr << '[';
if (!amts.empty())
@@ -243,7 +244,7 @@ struct FlowDebugInfo
}
ostr << ']';
};
auto writeNestedXrpAmtList =
auto const writeNestedXrpAmtList =
[&ostr, &writeXrpAmtList](std::vector<std::vector<EitherAmount>> const& amts) {
ostr << '[';
if (!amts.empty())
+4 -4
View File
@@ -25,7 +25,7 @@ checkFreeze(
XRPL_ASSERT(src != dst, "xrpl::checkFreeze : unequal input accounts");
// check freeze
if (auto sle = view.read(keylet::account(dst)))
if (auto const sle = view.read(keylet::account(dst)))
{
if (sle->isFlag(lsfGlobalFreeze))
{
@@ -33,7 +33,7 @@ checkFreeze(
}
}
if (auto sle = view.read(keylet::trustLine(src, dst, currency)))
if (auto const sle = view.read(keylet::trustLine(src, dst, currency)))
{
if (sle->isFlag((dst > src) ? lsfHighFreeze : lsfLowFreeze))
{
@@ -77,8 +77,8 @@ checkNoRipple(
beast::Journal j)
{
// fetch the ripple lines into and out of this node
auto sleIn = view.read(keylet::trustLine(prev, cur, currency));
auto sleOut = view.read(keylet::trustLine(cur, next, currency));
auto const sleIn = view.read(keylet::trustLine(prev, cur, currency));
auto const sleOut = view.read(keylet::trustLine(cur, next, currency));
if (!sleIn || !sleOut)
return terNO_LINE;
+2 -2
View File
@@ -647,7 +647,7 @@ flow(
// Returns std::nullopt if the aggregate overflows; callers treat that as a
// dry path.
auto sum = [](auto const& col) -> std::optional<std::decay_t<decltype(*col.begin())>> {
auto const sum = [](auto const& col) -> std::optional<std::decay_t<decltype(*col.begin())>> {
using TResult = std::decay_t<decltype(*col.begin())>;
if (col.empty())
return TResult{beast::kZero};
@@ -794,7 +794,7 @@ flow(
setUnion(ofrsToRmOnFail, ofrsToRm);
for (auto const& o : ofrsToRm)
{
if (auto ok = sb.peek(keylet::offer(o)))
if (auto const ok = sb.peek(keylet::offer(o)))
offerDelete(sb, ok, j);
}
}
@@ -36,25 +36,44 @@ public:
};
static constexpr std::array<FlagMapping, 7> flagMapping = {
{{.setFlag = tfMPTSetCanLock, .immutableFlag = lsifMPTCanLock, .ledgerFlag = lsfMPTCanLock},
{.setFlag = tfMPTSetRequireAuth,
.immutableFlag = lsifMPTRequireAuth,
.ledgerFlag = lsfMPTRequireAuth},
{.setFlag = tfMPTSetCanEscrow,
.immutableFlag = lsifMPTCanEscrow,
.ledgerFlag = lsfMPTCanEscrow},
{.setFlag = tfMPTSetCanTrade,
.immutableFlag = lsifMPTCanTrade,
.ledgerFlag = lsfMPTCanTrade},
{.setFlag = tfMPTSetCanTransfer,
.immutableFlag = lsifMPTCanTransfer,
.ledgerFlag = lsfMPTCanTransfer},
{.setFlag = tfMPTSetCanClawback,
.immutableFlag = lsifMPTCanClawback,
.ledgerFlag = lsfMPTCanClawback},
{.setFlag = tfMPTSetCanHoldConfidentialBalance,
.immutableFlag = lsifMPTCanHoldConfidentialBalance,
.ledgerFlag = lsfMPTCanHoldConfidentialBalance}}};
{
{
.setFlag = tfMPTSetCanLock,
.immutableFlag = lsifMPTCanLock,
.ledgerFlag = lsfMPTCanLock,
},
{
.setFlag = tfMPTSetRequireAuth,
.immutableFlag = lsifMPTRequireAuth,
.ledgerFlag = lsfMPTRequireAuth,
},
{
.setFlag = tfMPTSetCanEscrow,
.immutableFlag = lsifMPTCanEscrow,
.ledgerFlag = lsfMPTCanEscrow,
},
{
.setFlag = tfMPTSetCanTrade,
.immutableFlag = lsifMPTCanTrade,
.ledgerFlag = lsfMPTCanTrade,
},
{
.setFlag = tfMPTSetCanTransfer,
.immutableFlag = lsifMPTCanTransfer,
.ledgerFlag = lsfMPTCanTransfer,
},
{
.setFlag = tfMPTSetCanClawback,
.immutableFlag = lsifMPTCanClawback,
.ledgerFlag = lsfMPTCanClawback,
},
{
.setFlag = tfMPTSetCanHoldConfidentialBalance,
.immutableFlag = lsifMPTCanHoldConfidentialBalance,
.ledgerFlag = lsfMPTCanHoldConfidentialBalance,
},
},
};
static bool
checkExtraFeatures(PreflightContext const& ctx);
+7 -6
View File
@@ -203,14 +203,15 @@ makeRunner(Workload w, std::string cfg, std::shared_ptr<RunState> rs)
}
std::size_t index = state.thread_index();
for (auto _ : state)
for (auto const _ : state)
{
w.iterate(
IterateContext{
.rs = *rs,
.backend = *rs->harness->backend,
.index = index,
.poolSize = poolSize});
.poolSize = poolSize,
});
index += state.threads();
}
@@ -239,7 +240,7 @@ registerWorkload(BackendConfig const& bc, Workload const& w)
if (!w.pinToPool)
{
auto rs = std::make_shared<RunState>();
auto const rs = std::make_shared<RunState>();
benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs))
->RangeMultiplier(10)
->Range(kPoolSizes.front(), kPoolSizes.back())
@@ -258,7 +259,7 @@ registerWorkload(BackendConfig const& bc, Workload const& w)
if (poolSize % threads != 0)
continue;
auto rs = std::make_shared<RunState>();
auto const rs = std::make_shared<RunState>();
benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs))
->Arg(poolSize)
->Iterations(poolSize / threads)
@@ -287,7 +288,7 @@ registerStoreBatch(BackendConfig const& bc)
if (numBatches == 0)
continue;
auto rs = std::make_shared<RunState>();
auto const rs = std::make_shared<RunState>();
benchmark::RegisterBenchmark(
name,
[rs, cfg](benchmark::State& state) {
@@ -303,7 +304,7 @@ registerStoreBatch(BackendConfig const& bc)
}
std::size_t index = 0;
for (auto _ : state)
for (auto const _ : state)
{
rs->harness->backend->storeBatch(batches[index % batches.size()]);
++index;
@@ -192,7 +192,7 @@ Workload const kWork{
void
registerWorkload(BackendConfig const& bc, Workload const& w)
{
auto rs = std::make_shared<RunState>();
auto const rs = std::make_shared<RunState>();
std::string const cfg = bc.config;
std::string name{kNamePrefix};
name += w.name;
@@ -206,11 +206,16 @@ registerWorkload(BackendConfig const& bc, Workload const& w)
auto const seq = db.earliestLedgerSeq();
std::size_t index = 0;
for (auto _ : state)
for (auto const _ : state)
{
w.iterate(
IterateContext{
.rs = *rs, .db = db, .seq = seq, .index = index, .poolSize = poolSize});
.rs = *rs,
.db = db,
.seq = seq,
.index = index,
.poolSize = poolSize,
});
++index;
}
benchmark::ClobberMemory();
+1 -1
View File
@@ -25,7 +25,7 @@ CountedObjects::getCounts(int minimumThreshold) const
// might be temporarily less than the actual count.
counts.reserve(count_.load());
for (auto* ctr = head_.load(); ctr != nullptr; ctr = ctr->getNext())
for (auto const* ctr = head_.load(); ctr != nullptr; ctr = ctr->getNext())
{
if (ctr->getCount() >= minimumThreshold)
counts.emplace_back(ctr->getName(), ctr->getCount());
+1 -1
View File
@@ -302,7 +302,7 @@ Logs::format(
// Attempt to prevent sensitive information from appearing in log files by
// redacting it with asterisks.
auto scrubber = [&output](char const* token) {
auto const scrubber = [&output](char const* token) {
auto first = output.find(token);
// If we have found the specified token, then attempt to isolate the
Loaded 100 of 646 files, more files were not shown because too many files have changed in this diff. Show more