mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 08:46:46 +00:00
21 KiB
21 KiB
RPC
JSON-RPC over HTTP/WebSocket and gRPC. Central handler table dispatches by method name + API version. Roles: ADMIN, USER, IDENTIFIED, PROXY, FORBID, GUEST.
Key Invariants
- Handler table in
Handler.cpp: each entry ={name, function, role, condition, minApiVer, maxApiVer}asstd::multimap<string, Handler>. Same method name can have multiple entries with non-overlapping version ranges; overlap is a fatalLogicError()at startup. conditionMetchecks amendment-blocked, UNL expired, operating mode ≥ SYNCING, validated ledger age <Tuning::maxValidatedLedgerAge(2 min), and validated/current gap ≤ 10 ledgers. Standalone mode bypasses age checks.- API v1 vs v2 error code split: v1 emits
rpcNO_NETWORK/rpcNO_CURRENT/rpcNO_CLOSED; v2+ collapses torpcNOT_SYNCED. - Sensitive fields (
passphrase,secret,seed,seed_hex) are masked as<masked>in error response echoes. - Batch requests: top-level
"method": "batch"withparamsarray; each sub-request processed independently and accumulated into JSON array. Batch is rejected entirely if any sub-request is itself a batch (no nesting). - API v2 enforces strict JSON typing on previously-permissive boolean fields (e.g.,
signer_lists,binary,forward,transactions). - Two handler registration styles exist but only two handlers use the new-style (class-based):
LedgerHandlerandVersionHandler. All ~67 others use old-style free functions inhandlerArray.
Common Bug Patterns
- New handler without entry in
Handler.cppstatic array = handler silently unreachable. - Wrong
role_on handler: USER-level with admin data leaks; ADMIN handler accessible to users = security hole. conditionMetreturning false: ensure new conditions are documented and version-coded errors are paired.- Resource charging: each request gets a fee via
Resource::Consumer; missing charge allows DoS. maxRequestSize(1 MB) rejected before JSON parsing; oversized requests get no error detail.- Marker pagination: callers can forge markers pointing into other accounts' directories — always call
RPC::isRelatedToAccountbefore resuming. parse<T>()returningstd::nulloptis a programming-error sentinel for type system; user-facing errors go throughrequired<T>/Expected<T, Json::Value>.loadTypemust be set early in handler — escalates tofeeExceptionRPCautomatically on exception if stillfeeReferenceRPC.WSInfoSubonly trustsX-User/X-Forwarded-Forheaders when the remote IP is insecure_gateway_nets; outside that list, those headers are stripped. Misconfiguringsecure_gatewaylets untrusted clients spoof identity.RPCSub::sendThreadreadsmUsername/mPasswordoutsidemLock— minor data-race noted in source withXXXcomment.PathRequestManager::getAssetCacheassigns theweak_ptr<AssetCache>lock result to a localshared_ptrbefore returning — assigning directly to the weak member would cause immediate expiry.account_objectsmarker encodes phase (NFT page vs directory); resuming with a wrong-phase marker can traverse the wrong list. BothnftPageStartand directory marker use different sentinel shapes.
Adding New RPC Handler
- Declare in
Handlers.h:Json::Value doMyCommand(RPC::JsonContext&); - Implement in new file under
src/xrpld/rpc/handlers/<category>/. - Register in
Handler.cpphandlerArraywith role, condition, version range. - For class-based new-style handler (rare; only
LedgerHandler,VersionHandler): expose staticname,role,condition,minApiVer,maxApiVer; implementcheck()/writeResult(); register viaaddHandler<T>(). - For gRPC: define in
xrp_ledger.proto, addCallDatainGRPCServerImpl::setupListeners(), writedoXxxGrpc(RPC::GRPCContext<Request>&)returningstd::pair<Response, grpc::Status>.
Handler Patterns
Old-style registration (typical)
// In Handler.cpp handlerArray[] — REQUIRED for every new handler:
{"my_command", byRef(&doMyCommand), Role::USER, NO_CONDITION},
// role: ADMIN for internal/sensitive, USER for public
// condition: NO_CONDITION, NEEDS_NETWORK_CONNECTION, NEEDS_CURRENT_LEDGER, NEEDS_CLOSED_LEDGER
// version range defaults to [apiMinimumSupportedVersion, apiMaximumValidVersion]
// To version-bound: `{"ledger_header", byRef(&doLedgerHeader), Role::USER, NO_CONDITION, 1, 1}`
Version-Ranged Class Handler
// Class with static metadata; registered in HandlerTable ctor via addHandler<T>()
template <> Handler handlerFrom<MyCommandHandler>() {
return {MyCommandHandler::name, &handle<Json::Value, MyCommandHandler>,
MyCommandHandler::role, MyCommandHandler::condition,
MyCommandHandler::minApiVer, MyCommandHandler::maxApiVer};
}
Ledger Resolution
RPC::lookupLedger(ledger, context)for JSON path — handlesledger_hash/ledger_index/legacyledger/shortcut strings.RPC::ledgerFromRequest<T>(ledger, context)for gRPC.RPC::getOrAcquireLedger(context)returnsExpected<shared_ptr<Ledger const>, Json::Value>and triggersInboundLedgers::acquire()for missing ledgers (used only byledger_requestadmin command).
Pagination Idiom
- Marker format:
"<uint256_hex>,<uint64_pageHint>"for owner-directory handlers; raw hex for NFT page chains. - Request
limit + 1fromforEachItemAfter; ifcount == limit + 1, emit marker from limit-th item. - Always validate marker SLE belongs to requesting account before resuming.
- Limits from
RPC::Tuning::<command>clamped viareadLimitField(); admin/unlimited roles bypass clamp.
Key Files
Top-level
src/xrpld/rpc/handlers/Handlers.h— authoritative declarations of all old-style handler functions (~67 entries).src/xrpld/rpc/detail/Handler.cpp— handler table,getHandler(),HandlerTablesingleton, version overlap enforcement.src/xrpld/rpc/detail/Handler.h—Handlerstruct,Conditionenum,conditionMet()template.src/xrpld/rpc/detail/RPCHandler.cpp—doCommand()pipeline: load-shed, role check, condition check, perf-log instrumented dispatch.src/xrpld/rpc/detail/ServerHandler.cpp— HTTP/WS server entry; auth, batch handling, version-aware error formatting, secret masking, HTTP status from RPC error codes (ripplerpc ≥ 3.0).src/xrpld/rpc/RPCHandler.h—doCommand,roleRequireddeclarations.src/xrpld/rpc/Context.h—Context,JsonContext,GRPCContext<T>aggregate dispatch envelopes.src/xrpld/rpc/Request.h— simplerRequestenvelope (less used; lives alongsideContext).src/xrpld/rpc/Status.h— unified error type bridgingTER,error_code_i, and bare int withinject()to JSON.src/xrpld/rpc/Role.h—Roleenum,isUnlimited,requestRole,ipAllowed,forwardedFor.src/xrpld/rpc/detail/Role.cpp— IP subnet matching, secure_gateway resolution, RFC 7239 /X-Forwarded-Forparsing.src/xrpld/rpc/detail/RPCHelpers.cpp/.h— pagination, seed parsing, keypair derivation, ledger-entry type selection, MPT/IOU asset parsing.src/xrpld/rpc/detail/RPCLedgerHelpers.cpp/.h—lookupLedger,getLedger,getOrAcquireLedger; staleness checks; gRPCledgerFromSpecifier.src/xrpld/rpc/detail/Tuning.h— all numeric tunables (limits, ranges, throttles).include/xrpl/protocol/ErrorCodes.h—error_code_i,inject_error,ErrorInfotable, HTTP status mapping.
Subscriptions
src/xrpld/rpc/detail/WSInfoSub.h— WebSocketInfoSubsubclass;Json::stream-based zero-intermediate serialization intomulti_buffer. Only trustsX-User/X-Forwarded-Forwhen remote IP is insecure_gateway_nets.src/xrpld/rpc/RPCSub.h/detail/RPCSub.cpp— outbound HTTP/HTTPS push subscription ("webhook"); legacy feature maintained for one specific partner; carriesVFALCO TODOmarkers.sendThreadreadsmUsername/mPasswordoutsidemLock(data-race risk).- Streams:
server,ledger,book_changes,transactions,transactions_proposed(rt_transactionsdeprecated alias),validations,manifests,peer_status(admin),consensus. book_changesstream can be subscribed but cannot be unsubscribed — there is no unsubscribe path for it.account_history_tx_streamis experimental; gated onuseTxTables(); supportsstop_history_tx_onlyin unsubscribe.
Pathfinding
src/xrpld/rpc/detail/Pathfinder.cpp/.h— three-phase engine:findPaths()(template expansion via staticmPathTable),computePathRanks()(RippleCalc simulation),getBestPaths()(selection with covering-path).src/xrpld/rpc/detail/PathRequest.cpp/.h— per-request state machine; two constructors forpath_find(subscription) vsripple_path_find(legacy callback); adaptiveiLevel.src/xrpld/rpc/detail/PathRequestManager.cpp/.h— collection ofwptr<PathRequest>; re-entrantupdateAll()loop; sharedAssetCacheviaweak_ptr(intentional — cache lives only as long as a request holds it).src/xrpld/rpc/detail/AssetCache.cpp/.h— per-ledger thread-safe trust line + MPT cache; direction-superset optimization: caches lines in both directions (AB and BA) so a single fetch covers both source and destination queries;shared_ptr<vector<>>null sentinels for empty accounts.src/xrpld/rpc/detail/AccountAssets.cpp/.h—accountSourceAssets/accountDestAssetsfor path source/dest currency enumeration.src/xrpld/rpc/detail/TrustLine.cpp/.h—TrustLineBase+PathFindTrustLine(memory-minimal) +RPCTrustLine(adds quality rates).src/xrpld/rpc/detail/MPT.h—PathFindMPT(MPTID + zeroBalance + maxedOut); implicitly converts fromMPTIssuefor uniform interface withPathFindTrustLine.src/xrpld/rpc/detail/PathfinderUtils.h—largestAmount,convertAmount,convertAllCheck; XRP sentinel =STAmount::cMaxNative, IOU sentinel =STAmount::cMaxValue / cMaxOffset, MPT sentinel =maxMPTokenAmount. Pass these to signal "send all".src/xrpld/rpc/detail/LegacyPathFind.cpp/.h— RAII concurrency guard for synchronousripple_path_find; lock-free CAS oninProgresscounter; admin bypass. Destructor skips decrement if construction failed (m_isOkflag).src/xrpld/rpc/detail/RippleLineCache.cpp/.h— empty stubs; functionality replaced byAssetCache. Still#included in two files for inert compatibility.
Transaction Signing / Submission
src/xrpld/rpc/detail/TransactionSign.cpp/.h—transactionSign,transactionSubmit,transactionSignFor,transactionSubmitMultiSigned;SigningForParamsmode discriminator; round-trip "sterilization" viatransactionConstructImpl.- Fee pipeline:
checkFee→getCurrentNetworkFee(max of load-scaled base fee and TxQ-escalated open ledger fee, capped byfee_mult_max/fee_div_max). ProcessTransactionFndependency injection viagetProcessTxnFn(NetworkOPs&)for testability.acctMatchesPubKeyhandles three account states: unactivated (master-only), master+regular both valid, master disabled (regular only).doSubmit: pre-seedsHashRouterwith the transaction hash before forwarding, so the node does not rebroadcast its own submission.
Utility / Enrichment
src/xrpld/rpc/BookChanges.h— header-only templatecomputeBookChanges<L>(ledger); produces OHLCV per pair; reused by RPC handler andbook_changessubscription stream.src/xrpld/rpc/CTID.h— Concise Transaction ID (XLS-15d): 16-hexC+ 28-bit ledgerSeq + 16-bit txnIdx + 16-bit netID. Usesboost::regex, notstd::regex.src/xrpld/rpc/DeliveredAmount.h/detail/DeliveredAmount.cpp—insertDeliveredAmount; three-tier resolution; threshold = ledger 4594095 (Jan 2014) or close time 446000000s;"unavailable"string sentinel for pre-threshold ledgers; lazy lambdas avoidLedgerMastercalls when meta hassfDeliveredAmount.src/xrpld/rpc/MPTokenIssuanceID.h/detail/MPTokenIssuanceID.cpp—insertMPTokenIssuanceID; mirrorsDeliveredAmountthree-function pattern (eligibility / extraction / injection).src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp— built once at startup via Meyers singleton; SHA-512-half hash for client cache invalidation; X-macro–driven from protocol headers.src/xrpld/rpc/GRPCHandlers.h— declarations for 4 gRPC handlers (doLedgerGrpc,doLedgerEntryGrpc,doLedgerDataGrpc,doLedgerDiffGrpc). Contract: non-OKgrpc::Statusdiscards the response object.src/xrpld/rpc/Output.h—boost::utility/string_ref-based output sink. Vestigial; not used by current codebase (canonical sink isJson::Output).src/xrpld/rpc/json_body.h— Boost.BeastBodytype for JSON HTTP responses; bothreaderandwriterimplement BodyReader concept (eager, one-shot).
Client-side
src/xrpld/rpc/RPCCall.h/detail/RPCCall.cpp—xrpldCLI dispatch;RPCParserwith staticCommand[]table mapping method → parse function; "trusted interface" — minimal validation by design.
Enrichment Pipeline
Three functions are called together wherever transaction metadata is serialized to JSON. Always apply all three at the same call sites to keep responses consistent:
insertDeliveredAmount()— actual delivered amount for payments/check-cash/account-delete.RPC::insertNFTSyntheticInJson()— synthetic NFT fields (nft_offer_index,nftoken_id, etc.) extracted from metadata.RPC::insertMPTokenIssuanceID()—mpt_issuance_idfor successfulMPTokenIssuanceCreatetransactions.
Call sites: Tx.cpp, AccountTx.cpp, NetworkOPs.cpp, LedgerToJson.cpp, Simulate.cpp.
Resource Cost Tiers
Set context.loadType early. Tiers (from Fees.h):
feeReferenceRPC— default; auto-escalates tofeeExceptionRPCon uncaught exception.feeMediumBurdenRPC— directory walks, account_lines, account_offers, simulate, history paging, tx_reduce_relay-class ops.feeHeavyBurdenRPC— pathfinding, signing, gateway_balances, ledger_request, submit_multisigned.
Tuning Constants (in Tuning.h)
maxRequestSize = 1_000_000— rejected pre-parse inServerHandler.maxJobQueueClients = 500—RPCHandler::fillHandlerreturnsrpcTOO_BUSY; admin/unlimited bypass.maxValidatedLedgerAge = 2 min.maxPathfindsInProgress = 2,maxPathfindJobCount = 50,max_src_cur = 18,max_auto_src_cur = 88.binaryPageLength = 2048,jsonPageLength = 256— selected viapageLength(isBinary)inledger_data.- Per-command
LimitRange: most account queries{10, 200, 400};bookOffers {0, 60, 100};nftOffers {50, 250, 500};noRippleCheck {10, 300, 400};accountNFTokens {20, 100, 400}. defaultAutoFillFeeMultiplier = 10,defaultAutoFillFeeDivisor = 1.
Two-Tier Signing Access Gate
Sign-related handlers (sign, sign_for, submit with tx_json, channel_authorize) enforce:
if (context.role != Role::ADMIN && !context.app.config().canSign())
return rpcNOT_SUPPORTED;
canSign() reflects [signing_support] config; defaults false. Public nodes refuse to sign by default. All sign/sign_for responses include a deprecated warning steering clients to local/offline signing.
API Version Behavioral Differences
apiCommandLineVersionis used by the CLI; defaults differ from inbound.- v2 promotes fields from inside transaction objects to top-level:
hash,ledger_index,ledger_hash,close_time_iso. - v2 renames metadata keys:
tx→tx_json,meta→meta_blobfor binary. - v2 renames Payment
Amount→DeliverMax(viaRPC::insertDeliverMax). - v2 strict boolean typing; v1 silently coerces.
- v2 rejects mixing
ledger_index_min/maxwithledger_index/ledger_hashinaccount_tx; v1 tolerates. - v2 enforces precise marker objects in
account_tx({ledger, seq}integers). - v3 (beta) adds human-readable singleton aliases in
ledger_entryindex lookup.VersionHandler::maxApiVertracks the highest beta version; the width of the beta range (currently 1) matters for the version negotiation boundary.
Handler Subdirectory Map
handlers/account/—AccountInfo,AccountLines,AccountChannels,AccountCurrencies,AccountNFTs,AccountObjects,AccountOffers,AccountTx,GatewayBalances,NoRippleCheck,OwnerInfo(legacy).handlers/admin/—BlackList,UnlList, plus subdirectories fordata/(CanDelete, LedgerCleaner, LedgerRequest),keygen/(WalletPropose, ValidationCreate),log/,peer/,server_control/(Stop, LedgerAccept — standalone-only),signing/(ChannelAuthorize, Sign, SignFor),status/(ConsensusInfo, FetchInfo, GetCounts, Print, ValidatorInfo, Validators, ValidatorListSites).handlers/ledger/—Ledger(class-based),LedgerClosed,LedgerCurrent,LedgerData,LedgerDiff(gRPC-only),LedgerEntry(parser table fromledger_entries.macro),LedgerHeader.handlers/orderbook/—AMMInfo,BookChanges,BookOffers,DepositAuthorized,GetAggregatePrice,NFTBuyOffers/NFTSellOffers/NFTOffersHelpers.h,PathFind(subscription),RipplePathFind(one-shot).handlers/server_info/—Fee,Feature,Manifest,ServerDefinitions,ServerInfo,ServerState,Version.h(class-based).handlers/subscribe/—Subscribe,Unsubscribe.handlers/transaction/—Simulate(dry-run viatapDRY_RUN; batch rejected),Submit,SubmitMultiSigned,Tx,TransactionEntry(ledger-pinned),TxHistory(paginated,useTxTables()-gated, deep-page cap 10000 for non-admin),TxReduceRelay.handlers/utility/—Ping(role-conditional response),Random.- Top-level
handlers/:ChannelVerify(no admin restriction, stateless),VaultInfo(XLS-66, vault + MPT issuance lookup).
gRPC Specifics
- Four handlers:
Ledger,LedgerEntry,LedgerData(binary-only, fixed page=2048, supportsmarker+end_markerfor range parallelism),LedgerDiff(SHAMap delta; downcastReadView→Ledgeris the validation gate). doLedgerGrpcaddsget_objects(state diff viaSHAMap::compare) andget_object_neighbors(DEX best-offer tracking viakeylet::quality).- Handlers return
std::pair<Response, grpc::Status>. Non-OK status discards response. - Error mapping:
rpcINVALID_PARAMS→INVALID_ARGUMENT; ledger missing →NOT_FOUND; diff overflow →RESOURCE_EXHAUSTED.
Key Gotchas
noEvents(rpcNO_EVENTS) is returned bypath_findandsubscribe/unsubscribefor non-WebSocket transports — HTTP has no push channel.LegacyPathFindadmit-failure means destructor must not decrement; usesm_isOkflag.getMasterKeyreturns the input key unchanged when no manifest exists — used indoManifest/doValidatorInfoto distinguish "is master key" from "ephemeral with no manifest".ledger_acceptonly works in standalone mode; takes master mutex; drivesConsensus::simulate.ChannelAuthorize/ChannelVerifyuseHashPrefix::paymentChannelClaim('CLM') as domain separator; canonical message = prefix + 32-byte channelID + 8-byte drops.deposit_authorizedwith credentials: must sort(issuer, type)pairs canonically viacredentials::makeSortedbefore computing keylet;lifeExtendervector keeps SLEs alive soSliceviews intosfCredentialTyperemain valid.LedgerEntryusesExpected<uint256, Json::Value>parser return type rather than exceptions; v1 still re-throwsJson::errorfor compatibility.nft_buy_offers/nft_sell_offersdiffer only bykeylet::nft_buysvskeylet::nft_sells— both delegate toenumerateNFTOffersinNFTOffersHelpers.h.getCountsJson(inGetCounts.h) is callable from non-RPC contexts (e.g.,OverlayImpl::getCountsJson).wallet_proposeentropy warning: <80 bits → strong warning; passphrase that already encodes the seed (1751/Base58/hex) suppresses warning.- Account marker security: always verify
RPC::isRelatedToAccount(*ledger, sle, accountID)on resumed pagination — prevents cross-account directory traversal. AMMInfohas two distinct parsing paths: one foramm_account(direct lookup) and one forasset+asset2pair (derives AMM account viakeylet::amm). Both resolve to the same AMM SLE but through different code paths — changes must update both.book_offersapplies an inline load-shedder: ifcheckFeedetermines the consumer is at warning/drop tier, it cuts the offer limit in half before iterating.Simulaterejects batch transactions (returnsrpcINVALID_PARAMS) —tapDRY_RUNdoes not support the batch transaction type.autofillSignature()inSimulate.cppremovesSigningPubKeyandTxnSignaturefields before applying dry-run, then restores them; callers must not pre-sign before calling Simulate.RPCSubis a legacy feature retained for one specific partner. It is not a general-purpose webhook system. New subscribers should use WebSocket instead.