perf(rpc): hash pathfind accounts only when the span records them

doPathFind and doRipplePathFind fill two span attributes from the request's
source and destination accounts. Both values are call arguments, so they are
built whatever the build: asString() copies the address out of the JSON and
redactAccount() takes a SHA-512Half over it and formats 16 hex characters.
That is two copies and two hashes on every pathfinding RPC, for
pathfind_source_account and pathfind_dest_account, which nothing outside the
span reads.

Wrapping the block in "if (span)" drops that work when telemetry is compiled
out, where the guard's operator bool() is a literal false, and also when
telemetry is on but this span is not being recorded. The const-reference read
of context.params moves inside the guard with the code that needs it, so a
telemetry read still never inserts a null into the request.
This commit is contained in:
Pratik Mankawde
2026-08-26 19:48:38 +01:00
parent 12e0fddeae
commit fcdbbabedf
2 changed files with 42 additions and 20 deletions

View File

@@ -25,16 +25,27 @@ doPathFind(rpc::JsonContext& context)
// thread) nest under it. doPathFind does not yield, so scoping is safe.
auto span = ScopedSpanGuard(
TraceCategory::Rpc, pathfind_span::prefix::pathfind, pathfind_span::op::request);
// Addresses are hashed before emission for privacy. Read through a const
// reference: the non-const json::Value::operator[] inserts a null for a
// missing key, which would make PathRequest::parseJson's isMember() checks
// see an absent field as present and return Malformed instead of Missing.
// Reading for telemetry must not alter what the request looks like.
auto const& params = std::as_const(context.params);
if (auto const& src = params[jss::source_account]; src.isString())
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
if (auto const& dst = params[jss::destination_account]; dst.isString())
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
// Guarded on the span being live because setAttribute's arguments are
// evaluated whatever the build, and neither is free: asString() copies the
// address out of the JSON and redactAccount() takes a SHA-512Half over it.
// That is two copies and two hashes on every path_find call. The
// compiled-out guard's operator bool() is a literal false, so the block
// disappears entirely in that build; with telemetry compiled in it is
// skipped whenever this span is not being recorded.
if (span)
{
// Addresses are hashed before emission for privacy. Read through a
// const reference: the non-const json::Value::operator[] inserts a null
// for a missing key, which would make PathRequest::parseJson's
// isMember() checks see an absent field as present and return Malformed
// instead of Missing. Reading for telemetry must not alter what the
// request looks like.
auto const& params = std::as_const(context.params);
if (auto const& src = params[jss::source_account]; src.isString())
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
if (auto const& dst = params[jss::destination_account]; dst.isString())
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
}
if (context.app.config().pathSearchMax == 0)
return rpcError(RpcNotSupported);

View File

@@ -34,16 +34,27 @@ doRipplePathFind(rpc::JsonContext& context)
// span's log lines stay trace-correlated.
auto span = ScopedSpanGuard(
TraceCategory::Rpc, pathfind_span::prefix::pathfind, pathfind_span::op::request);
// Addresses are hashed before emission for privacy. Read through a const
// reference: the non-const json::Value::operator[] inserts a null for a
// missing key, which would make PathRequest::parseJson's isMember() checks
// see an absent field as present and return Malformed instead of Missing.
// Reading for telemetry must not alter what the request looks like.
auto const& params = std::as_const(context.params);
if (auto const& src = params[jss::source_account]; src.isString())
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
if (auto const& dst = params[jss::destination_account]; dst.isString())
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
// Guarded on the span being live because setAttribute's arguments are
// evaluated whatever the build, and neither is free: asString() copies the
// address out of the JSON and redactAccount() takes a SHA-512Half over it.
// That is two copies and two hashes on every ripple_path_find call. The
// compiled-out guard's operator bool() is a literal false, so the block
// disappears entirely in that build; with telemetry compiled in it is
// skipped whenever this span is not being recorded.
if (span)
{
// Addresses are hashed before emission for privacy. Read through a
// const reference: the non-const json::Value::operator[] inserts a null
// for a missing key, which would make PathRequest::parseJson's
// isMember() checks see an absent field as present and return Malformed
// instead of Missing. Reading for telemetry must not alter what the
// request looks like.
auto const& params = std::as_const(context.params);
if (auto const& src = params[jss::source_account]; src.isString())
span.setAttribute(pathfind_span::attr::sourceAccount, redactAccount(src.asString()));
if (auto const& dst = params[jss::destination_account]; dst.isString())
span.setAttribute(pathfind_span::attr::destAccount, redactAccount(dst.asString()));
}
if (context.app.config().pathSearchMax == 0)
return rpcError(RpcNotSupported);