mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 14:20:56 +00:00
Telemetry must read state, never change it. Two defects here did change it, plus three smaller correctness and privacy fixes. doPathFind and doRipplePathFind read source_account / destination_account off context.params to hash them into span attributes. context.params is non-const, so those reads selected json::Value's non-const operator[], which inserts a null for a missing key. The same object is later validated by PathRequest::parseJson, whose first checks are isMember(source_account) and isMember(destination_account) — so a request that omitted either field looked present and the client received Malformed instead of Missing. Reads now go through std::as_const, whose overload returns kNull without inserting. PathRequest::doUpdate emitted pathfind_dest_currency as to_string(saDstAmount_.asset()). For a non-XRP asset that renders as "<issuer>/<currency>" with the issuer as a plaintext Base58 address, so a plain account address reached the span pipeline even though every other account here is hashed first. The issuer is now redacted and the currency kept; an MPT asset renders as its issuance ID and carries no address. PathRequestManager::updateAll created pathfind.update_all with an unscoped SpanGuard. An unscoped guard takes the ambient span as its own parent but does not itself become the ambient parent, so the pathfind.compute spans that doUpdate creates never nested under it, contradicting the documented hierarchy. It is now a scoped guard, held in std::optional because ScopedSpanGuard is deliberately non-movable and so cannot be produced by a ternary. The skip when there are no active subscriptions is preserved. updateAll is dispatched via addJob and doUpdate runs synchronously, so the guard is constructed and destroyed under the same context store, as ScopedSpanGuard requires. The WebSocket entry point emitted the client-supplied command string directly. That value becomes a Prometheus label, so arbitrary request input could drive unbounded label cardinality. It is now resolved against the handler registry, collapsing anything unrecognized to "unknown", matching what the HTTP path already does. Also: the pathfind.discover comment claimed future child spans could be parented off it, which its unscoped guard cannot do — corrected to say what would be required instead. Config-reference and task-list docs named the parser setupTelemetry(); the API is makeTelemetrySetup().
89 lines
2.9 KiB
C++
89 lines
2.9 KiB
C++
#include <xrpld/app/ledger/LedgerMaster.h>
|
|
#include <xrpld/app/main/Application.h>
|
|
#include <xrpld/rpc/Context.h>
|
|
#include <xrpld/rpc/detail/PathFindSpanNames.h>
|
|
#include <xrpld/rpc/detail/PathRequestManager.h>
|
|
|
|
#include <xrpl/json/json_value.h>
|
|
#include <xrpl/protocol/ErrorCodes.h>
|
|
#include <xrpl/protocol/RPCErr.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
#include <xrpl/resource/Fees.h>
|
|
#include <xrpl/server/InfoSub.h>
|
|
#include <xrpl/telemetry/Redaction.h>
|
|
#include <xrpl/telemetry/SpanGuard.h>
|
|
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
json::Value
|
|
doPathFind(RPC::JsonContext& context)
|
|
{
|
|
using namespace telemetry;
|
|
// Scoped so pathfind.compute/discover (created synchronously below on this
|
|
// 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()));
|
|
|
|
if (context.app.config().pathSearchMax == 0)
|
|
return rpcError(RpcNotSupported);
|
|
|
|
auto lpLedger = context.ledgerMaster.getClosedLedger();
|
|
|
|
if (!context.params.isMember(jss::subcommand) || !context.params[jss::subcommand].isString())
|
|
{
|
|
return rpcError(RpcInvalidParams);
|
|
}
|
|
|
|
if (!context.infoSub)
|
|
return rpcError(RpcNoEvents);
|
|
|
|
context.infoSub->setApiVersion(context.apiVersion);
|
|
|
|
auto sSubCommand = context.params[jss::subcommand].asString();
|
|
|
|
if (sSubCommand == "create")
|
|
{
|
|
context.loadType = Resource::kFeeHeavyBurdenRpc;
|
|
context.infoSub->clearRequest();
|
|
return context.app.getPathRequestManager().makePathRequest(
|
|
context.infoSub, lpLedger, context.params);
|
|
}
|
|
|
|
if (sSubCommand == "close")
|
|
{
|
|
InfoSubRequest::pointer const request = context.infoSub->getRequest();
|
|
|
|
if (!request)
|
|
return rpcError(RpcNoPfRequest);
|
|
|
|
context.infoSub->clearRequest();
|
|
return request->doClose();
|
|
}
|
|
|
|
if (sSubCommand == "status")
|
|
{
|
|
InfoSubRequest::pointer const request = context.infoSub->getRequest();
|
|
|
|
if (!request)
|
|
return rpcError(RpcNoPfRequest);
|
|
|
|
return request->doStatus(context.params);
|
|
}
|
|
|
|
return rpcError(RpcInvalidParams);
|
|
}
|
|
|
|
} // namespace xrpl
|