Compare commits

..
Author SHA1 Message Date
tequ 86ac135c63 refactor: include transactor headers from transactions.macro
Partial cherry-pick of XRPLF/rippled#5590. Each TRANSACTION entry now
carries its transactor header behind `#if TRANSACTION_INCLUDE`, and
applySteps.cpp pulls them in by expanding the macro with
TRANSACTION_INCLUDE defined. Adding a transaction type no longer
requires touching the include list in applySteps.cpp.

ApplyContext.h (reached via Transactor.h) and XahauGenesis.h (unused
there) are dropped from applySteps.cpp.
2026-10-22 15:44:30 +09:00
26 changed files with 234 additions and 1809 deletions

No files matched your search

-62
View File
@@ -848,68 +848,6 @@
#
#
#
# [lite_mode]
#
# Run as a lite node: follow the network's validated ledgers without
# holding their state. For each validated ledger the server fetches the
# header, the whole transaction map (small, and what account subscriptions
# and `tx` answer from) and only the state nodes on the paths from the root
# to the objects in [lite_watch], and to the objects the transactions in
# that ledger created or touched for the watched accounts. Everything else
# stays on the network. Sync takes a few request/response rounds, not
# hours, and a ledger costs a few kilobytes rather than the full state.
#
# This is for small devices that need to see whether a transaction
# succeeded, read a balance or a trust line, or submit a transaction, and
# do not need to serve other nodes. In lite mode the server:
#
# - takes no part in consensus and never closes a ledger of its own;
# - serves no ledger data to peers and advertises no ledger range;
# - fetches no history: only ledgers validated after it started;
# - implies [node_db] type=rwdb (nothing is written to a node store;
# the last ledger_history ledgers are kept in memory), path_search_max
# 0, and refuses [validation_seed] / [validator_token] and standalone;
# - answers `ledger_index: current` and `closed` with the validated
# ledger;
# - relays a submitted transaction after checking its form and
# signature, and reports terLITE_RELAYED instead of applying it: read
# the outcome with `tx` or an account subscription;
# - answers a request for any object it does not hold with
# liteNotWatched. Add the object with the lite_watch RPC; it is read
# from the next validated ledger.
#
# Legal values are 0 (the default) and 1.
#
# Suggested companions for a device with little memory:
#
# [node_size] tiny
# [ledger_history] 64
# [peers_max] 3
#
# [lite_watch]
#
# One entry per line: a base58 account address (its AccountRoot is watched,
# and the objects its transactions create or touch are discovered as they
# happen) or the 64 hex digit key of any ledger object. The fee settings,
# amendments, skip list and negative UNL are always watched.
#
# Entries can be added and removed while running with the lite_watch and
# lite_unwatch admin RPCs, which also take trust lines by their parties;
# lite_status lists what is watched and why.
#
# [lite_discover]
#
# Whether a lite node grows its watch set from the transactions it sees:
# an affected object naming a watched account is watched from then on, and
# a transaction that touched anything watched has every object it created
# watched (the trust line, escrow, check, offer, URIToken or hook state it
# made for the account). Deleted objects are forgotten again.
#
# Legal values are 1 (the default) and 0, which watches only what is
# configured.
#
#
#
# [ledger_history]
#
# The number of past ledgers to acquire on server startup and the minimum to
+1 -5
View File
@@ -157,11 +157,7 @@ enum error_code_i {
// Simulate
rpcTX_SIGNED = 97,
// Lite mode: the object is not among the watched keys, so this node
// does not hold it
rpcLITE_NOT_WATCHED = 98,
rpcLAST = rpcLITE_NOT_WATCHED // rpcLAST should always equal the last code.
rpcLAST = rpcTX_SIGNED // rpcLAST should always equal the last code.
};
/** Codes returned in the `warnings` array of certain RPC commands.
+1 -4
View File
@@ -238,11 +238,8 @@ enum TERcodes : TERUnderlyingType {
terQUEUED, // Transaction is being held in TxQ until fee drops
terPRE_TICKET, // Ticket is not yet in ledger but might be on its way
terNO_AMM, // AMM doesn't exist for the asset pair
terNO_HOOK, // Transaction requires a non-existent hook definition
terNO_HOOK // Transaction requires a non-existent hook definition
// (referenced by sfHookHash)
terLITE_RELAYED // Relayed by a [lite_mode] node that holds no state to
// apply it against; the result is in the validated
// ledger it turns up in, if any
};
//------------------------------------------------------------------------------
+167 -1
View File
@@ -24,11 +24,21 @@
/**
* TRANSACTION(tag, value, name, fields)
*
* To ease maintenance, you may replace any unneeded values with "..."
* e.g. #define TRANSACTION(tag, value, name, ...)
*
* You must define a transactor class in the `ripple` namespace named `name`,
* and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`.
* and include its header alongside the TRANSACTOR definition using this
* format:
* #if TRANSACTION_INCLUDE
* # include <xrpld/app/tx/detail/HEADER.h>
* #endif
*/
/** This transaction type executes a payment. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Payment.h>
#endif
TRANSACTION(ttPAYMENT, 0, Payment, ({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
@@ -41,6 +51,9 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
}))
/** This transaction type creates an escrow object. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
#endif
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
@@ -62,6 +75,9 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
/** This transaction type adjusts various account settings. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetAccount.h>
#endif
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
{sfEmailHash, soeOPTIONAL},
{sfWalletLocator, soeOPTIONAL},
@@ -85,6 +101,9 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, ({
}))
/** This transaction type sets or clears an account's "regular key". */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetRegularKey.h>
#endif
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
{sfRegularKey, soeOPTIONAL},
}))
@@ -92,6 +111,9 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
// 6 deprecated
/** This transaction type creates an offer to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateOffer.h>
#endif
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
{sfTakerPays, soeREQUIRED},
{sfTakerGets, soeREQUIRED},
@@ -101,6 +123,9 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
}))
/** This transaction type cancels existing offers to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelOffer.h>
#endif
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
{sfOfferSequence, soeOPTIONAL},
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
@@ -109,6 +134,9 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
// 9 deprecated
/** This transaction type creates a new set of tickets. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateTicket.h>
#endif
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
{sfTicketCount, soeREQUIRED},
}))
@@ -118,12 +146,18 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
/** This transaction type modifies the signer list associated with an account. */
// The SignerEntries are optional because a SignerList is deleted by
// setting the SignerQuorum to zero and omitting SignerEntries.
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetSignerList.h>
#endif
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({
{sfSignerQuorum, soeREQUIRED},
{sfSignerEntries, soeOPTIONAL},
}))
/** This transaction type creates a new unidirectional XRP payment channel. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PayChan.h>
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
@@ -151,6 +185,9 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({
}))
/** This transaction type creates a new check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateCheck.h>
#endif
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
{sfDestination, soeREQUIRED},
{sfSendMax, soeREQUIRED},
@@ -160,6 +197,9 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
}))
/** This transaction type cashes an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CashCheck.h>
#endif
TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
{sfCheckID, soeREQUIRED},
{sfAmount, soeOPTIONAL},
@@ -167,11 +207,17 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
}))
/** This transaction type cancels an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelCheck.h>
#endif
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({
{sfCheckID, soeREQUIRED},
}))
/** This transaction type grants or revokes authorization to transfer funds. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DepositPreauth.h>
#endif
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
{sfAuthorize, soeOPTIONAL},
{sfUnauthorize, soeOPTIONAL},
@@ -180,6 +226,9 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
}))
/** This transaction type modifies a trustline between two accounts. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetTrust.h>
#endif
TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
{sfLimitAmount, soeOPTIONAL},
{sfQualityIn, soeOPTIONAL},
@@ -187,6 +236,9 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
}))
/** This transaction type deletes an existing account. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteAccount.h>
#endif
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
{sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
@@ -194,11 +246,17 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
}))
/** This transaction type installs a hook. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetHook.h>
#endif
TRANSACTION(ttHOOK_SET, 22, SetHook, ({
{sfHooks, soeREQUIRED},
}))
/** This transaction mints a new NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenMint.h>
#endif
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
{sfNFTokenTaxon, soeREQUIRED},
{sfTransferFee, soeOPTIONAL},
@@ -210,12 +268,18 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
}))
/** This transaction burns (i.e. destroys) an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenBurn.h>
#endif
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({
{sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL},
}))
/** This transaction creates a new offer to buy or sell an NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
{sfNFTokenID, soeREQUIRED},
{sfAmount, soeREQUIRED},
@@ -225,11 +289,17 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
}))
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({
{sfNFTokenOffers, soeREQUIRED},
}))
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
#endif
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
{sfNFTokenBuyOffer, soeOPTIONAL},
{sfNFTokenSellOffer, soeOPTIONAL},
@@ -237,12 +307,18 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
}))
/** This transaction claws back issued tokens. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Clawback.h>
#endif
TRANSACTION(ttCLAWBACK, 30, Clawback, ({
{sfAmount, soeREQUIRED, soeMPTSupported},
{sfHolder, soeOPTIONAL},
}))
/** This transaction claws back tokens from an AMM pool. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMClawback.h>
#endif
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
{sfHolder, soeREQUIRED},
{sfAsset, soeREQUIRED},
@@ -251,6 +327,9 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
}))
/** This transaction type creates an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMCreate.h>
#endif
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
{sfAmount, soeREQUIRED},
{sfAmount2, soeREQUIRED},
@@ -258,6 +337,9 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
}))
/** This transaction type deposits into an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDeposit.h>
#endif
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -269,6 +351,9 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
}))
/** This transaction type withdraws from an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMWithdraw.h>
#endif
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -279,6 +364,9 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
}))
/** This transaction type votes for the trading fee */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMVote.h>
#endif
TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -286,6 +374,9 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
}))
/** This transaction type bids for the auction slot */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMBid.h>
#endif
TRANSACTION(ttAMM_BID, 39, AMMBid, ({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
@@ -295,12 +386,18 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, ({
}))
/** This transaction type deletes AMM in the empty state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDelete.h>
#endif
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
}))
/** This transaction mints/burns/buys/sells a URI TOKEN */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/URIToken.h>
#endif
TRANSACTION(ttURITOKEN_MINT, 45, URITokenMint, ({
{sfURI, soeREQUIRED},
{sfDigest, soeOPTIONAL},
@@ -328,6 +425,9 @@ TRANSACTION(ttURITOKEN_CANCEL_SELL_OFFER, 49, URITokenCancelSellOffer, ({
}))
/** This transactions creates a crosschain sequence number */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/XChainBridge.h>
#endif
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 50, XChainCreateClaimID, ({
{sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
@@ -407,6 +507,9 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 57, XChainCreateBridge, ({
}))
/** This transaction type creates or updates a DID */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DID.h>
#endif
TRANSACTION(ttDID_SET, 58, DIDSet, ({
{sfDIDDocument, soeOPTIONAL},
{sfURI, soeOPTIONAL},
@@ -417,6 +520,9 @@ TRANSACTION(ttDID_SET, 58, DIDSet, ({
TRANSACTION(ttDID_DELETE, 59, DIDDelete, ({}))
/** This transaction type creates an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetOracle.h>
#endif
TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
{sfOracleDocumentID, soeREQUIRED},
{sfProvider, soeOPTIONAL},
@@ -427,17 +533,26 @@ TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
}))
/** This transaction type deletes an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteOracle.h>
#endif
TRANSACTION(ttORACLE_DELETE, 61, OracleDelete, ({
{sfOracleDocumentID, soeREQUIRED},
}))
/** This transaction type fixes a problem in the ledger state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LedgerStateFix.h>
#endif
TRANSACTION(ttLEDGER_STATE_FIX, 62, LedgerStateFix, ({
{sfLedgerFixType, soeREQUIRED},
{sfOwner, soeOPTIONAL},
}))
/** This transaction type creates a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
{sfAssetScale, soeOPTIONAL},
{sfTransferFee, soeOPTIONAL},
@@ -446,23 +561,35 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
}))
/** This transaction type destroys a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 64, MPTokenIssuanceDestroy, ({
{sfMPTokenIssuanceID, soeREQUIRED},
}))
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 65, MPTokenIssuanceSet, ({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL},
}))
/** This transaction type authorizes a MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#endif
TRANSACTION(ttMPTOKEN_AUTHORIZE, 66, MPTokenAuthorize, ({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL},
}))
/** This transaction type create an Credential instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Credentials.h>
#endif
TRANSACTION(ttCREDENTIAL_CREATE, 67, CredentialCreate, ({
{sfSubject, soeREQUIRED},
{sfCredentialType, soeREQUIRED},
@@ -484,6 +611,9 @@ TRANSACTION(ttCREDENTIAL_DELETE, 69, CredentialDelete, ({
}))
/** This transaction type modify a NFToken */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenModify.h>
#endif
TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
{sfNFTokenID, soeREQUIRED},
{sfOwner, soeOPTIONAL},
@@ -491,12 +621,18 @@ TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
}))
/** This transaction type creates or modifies a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 71, PermissionedDomainSet, ({
{sfDomainID, soeOPTIONAL},
{sfAcceptedCredentials, soeREQUIRED},
}))
/** This transaction type deletes a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
{sfDomainID, soeREQUIRED},
}))
@@ -510,6 +646,9 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
open ledger, so a reveal only becomes public after the revealing
validator's transaction set is locked.
*/
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Change.h>
#endif
TRANSACTION(ttENTROPY, 89, Entropy, ({
{sfLedgerSequence, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
@@ -519,17 +658,26 @@ TRANSACTION(ttENTROPY, 89, Entropy, ({
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
makes the SOTemplate reject the format at construction. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetManifest.h>
#endif
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
{sfManifest, soeREQUIRED},
}))
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Cron.h>
#endif
TRANSACTION(ttCRON, 92, Cron, ({
{sfOwner, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},
}))
/* Sechedule an alarm for later */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CronSet.h>
#endif
TRANSACTION(ttCRON_SET, 93, CronSet, ({
{sfDelaySeconds, soeOPTIONAL},
{sfRepeatCount, soeOPTIONAL},
@@ -537,6 +685,9 @@ TRANSACTION(ttCRON_SET, 93, CronSet, ({
}))
/* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetRemarks.h>
#endif
TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
{sfObjectID, soeREQUIRED},
{sfRemarks, soeREQUIRED},
@@ -544,6 +695,9 @@ TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
* that the sender pays for. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Remit.h>
#endif
TRANSACTION(ttREMIT, 95, Remit, ({
{sfDestination, soeREQUIRED},
{sfAmounts, soeOPTIONAL},
@@ -558,12 +712,18 @@ TRANSACTION(ttREMIT, 95, Remit, ({
/** This transaction can only be used by the genesis account, which is controlled exclusively by
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,
* according to reward schedule */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/GenesisMint.h>
#endif
TRANSACTION(ttGENESIS_MINT, 96, GenesisMint, ({
{sfGenesisMints, soeREQUIRED},
}))
/** This transaction accepts a proof of burn from an external network as a basis
* for minting according to featureImport */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Import.h>
#endif
TRANSACTION(ttIMPORT, 97, Import, ({
{sfBlob, soeREQUIRED},
{sfIssuer, soeOPTIONAL},
@@ -571,12 +731,18 @@ TRANSACTION(ttIMPORT, 97, Import, ({
/** This transaction resets accumulator/counters and claims a reward for holding an average balance
* from a specified hook */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/ClaimReward.h>
#endif
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
{sfIssuer, soeOPTIONAL},
{sfClaimCurrency, soeOPTIONAL},
}))
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Invoke.h>
#endif
TRANSACTION(ttINVOKE, 99, Invoke, ({
{sfBlob, soeOPTIONAL},
{sfDestination, soeOPTIONAL},
+1 -2
View File
@@ -113,8 +113,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
{rpcLEDGER_MISSING, "ledgerMissing", "One or more ledgers in the specified range is missing", 406},
{rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
{rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400},
{rpcLITE_NOT_WATCHED, "liteNotWatched", "This lite node does not watch that ledger object; add it with lite_watch.", 404}};
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
// clang-format on
// Sort and validate unorderedErrorInfos at compile time. Should be
-1
View File
@@ -242,7 +242,6 @@ transResults()
MAKE_ERROR(terPRE_TICKET, "Ticket is not yet in ledger."),
MAKE_ERROR(terNO_HOOK, "No hook with that hash exists on the ledger."),
MAKE_ERROR(terNO_AMM, "AMM doesn't exist for the asset pair."),
MAKE_ERROR(terLITE_RELAYED, "Relayed to the network by a lite node; not applied locally."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
MAKE_ERROR(tesPARTIAL, "The transaction was applied but should be submitted again until returning tesSUCCESS."),
-282
View File
@@ -1,282 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx.h>
#include <test/shamap/common.h>
#include <xrpld/app/ledger/Ledger.h>
#include <xrpld/app/ledger/LiteWatch.h>
#include <xrpld/core/Config.h>
#include <xrpld/shamap/SHAMap.h>
#include <xrpl/protocol/Indexes.h>
namespace ripple {
namespace test {
class LiteWatch_test : public beast::unit_test::suite
{
static Config
liteConfig(std::vector<std::string> watch)
{
Config cfg;
cfg.LITE_MODE = true;
cfg.LITE_WATCH = std::move(watch);
return cfg;
}
void
testConfigLines()
{
testcase("config lines");
using namespace jtx;
Account const alice{"alice"};
std::string err;
auto const acct = parseLiteWatchLine(" " + alice.human() + " ", err);
BEAST_EXPECT(acct && acct->first && *acct->first == alice.id());
BEAST_EXPECT(acct && acct->second == keylet::account(alice).key);
auto const key = keylet::fees().key;
auto const raw = parseLiteWatchLine(to_string(key), err);
BEAST_EXPECT(raw && !raw->first && raw->second == key);
BEAST_EXPECT(!parseLiteWatchLine("", err));
BEAST_EXPECT(!parseLiteWatchLine("not a key", err));
BEAST_EXPECT(!parseLiteWatchLine(to_string(key).substr(1), err));
}
void
testWatchSet()
{
testcase("watch set");
using namespace jtx;
Account const alice{"alice"};
Account const bob{"bob"};
Env env{*this};
LiteWatch watch{liteConfig({alice.human()}), env.journal};
BEAST_EXPECT(watch.enabled());
BEAST_EXPECT(watch.isWatchedAccount(alice));
BEAST_EXPECT(watch.isWatched(keylet::account(alice).key));
BEAST_EXPECT(!watch.isWatchedAccount(bob));
// The keys every ledger needs are there, and cannot be removed.
for (auto const& k :
{keylet::fees().key,
keylet::amendments().key,
keylet::skip().key,
keylet::negativeUNL().key})
{
BEAST_EXPECT(watch.isWatched(k));
BEAST_EXPECT(!watch.unwatchKey(k));
BEAST_EXPECT(watch.isWatched(k));
}
BEAST_EXPECT(watch.isWatched(keylet::hook(alice).key));
BEAST_EXPECT(watch.size() == 6);
BEAST_EXPECT(watch.watchAccount(bob));
BEAST_EXPECT(!watch.watchAccount(bob));
BEAST_EXPECT(watch.isWatched(keylet::account(bob).key));
BEAST_EXPECT(watch.unwatchAccount(bob));
BEAST_EXPECT(!watch.isWatched(keylet::account(bob).key));
BEAST_EXPECT(!watch.isWatched(keylet::hook(bob).key));
BEAST_EXPECT(!watch.unwatchAccount(bob));
auto const k = keylet::ownerDir(alice).key;
BEAST_EXPECT(watch.watchKey(k, LiteWatch::Tier::discovered, "test"));
BEAST_EXPECT(!watch.watchKey(k));
// Asking for a discovered key by name promotes it: it now survives
// the account it was discovered through being unwatched.
BEAST_EXPECT(watch.unwatchAccount(alice));
BEAST_EXPECT(watch.isWatched(k));
BEAST_EXPECT(watch.unwatchKey(k));
BEAST_EXPECT(!watch.isWatched(k));
auto const json = watch.getJson();
BEAST_EXPECT(json[jss::enabled].asBool());
BEAST_EXPECT(json["system"].asUInt() == 4);
}
void
testDiscover()
{
testcase("discover");
using namespace jtx;
Account const gw{"gw"};
Account const alice{"alice"};
Account const bob{"bob"};
auto const USD = gw["USD"];
Env env{*this};
env.fund(XRP(10000), gw, alice, bob);
env.close();
LiteWatch watch{liteConfig({alice.human()}), env.journal};
auto const line = keylet::line(alice, gw, USD.currency).key;
auto const bobLine = keylet::line(bob, gw, USD.currency).key;
// Nothing in the funding ledger concerns alice's objects beyond her
// root, which is configured already.
watch.discover(*env.closed());
BEAST_EXPECT(!watch.isWatched(keylet::account(bob).key));
BEAST_EXPECT(!watch.isWatched(line));
env.trust(USD(100), alice);
env.trust(USD(100), bob);
env(pay(gw, alice, USD(50)));
env.close();
auto const added = watch.discover(*env.closed());
BEAST_EXPECT(added > 0);
// alice's new trust line names her as a party; bob's does not.
BEAST_EXPECT(watch.isWatched(line));
BEAST_EXPECT(!watch.isWatched(bobLine));
BEAST_EXPECT(!watch.isWatched(keylet::account(bob).key));
// Her owner directory names her as its owner.
BEAST_EXPECT(watch.isWatched(keylet::ownerDir(alice).key));
// Paying the line back to zero and clearing the limit deletes it,
// and a discovered object that is deleted is forgotten.
env(pay(alice, gw, USD(50)));
env(trust(alice, USD(0)));
env.close();
watch.discover(*env.closed());
BEAST_EXPECT(!watch.isWatched(line));
BEAST_EXPECT(watch.isWatched(keylet::account(alice).key));
// With discovery off nothing is learned.
auto cfg = liteConfig({alice.human()});
cfg.LITE_DISCOVER = false;
LiteWatch quiet{cfg, env.journal};
env.trust(USD(100), alice);
env.close();
BEAST_EXPECT(quiet.discover(*env.closed()) == 0);
BEAST_EXPECT(!quiet.isWatched(line));
}
void
testPartialSync()
{
testcase("partial sync");
using namespace jtx;
Account const gw{"gw"};
Account const alice{"alice"};
Account const bob{"bob"};
auto const USD = gw["USD"];
Env env{*this};
env.fund(XRP(10000), gw, alice, bob);
env.trust(USD(100), alice);
env(pay(gw, alice, USD(50)));
env.close();
auto const ledger =
std::dynamic_pointer_cast<Ledger const>(env.closed());
if (!BEAST_EXPECT(ledger))
return;
auto const& full = ledger->stateMap();
LiteWatch watch{liteConfig({alice.human()}), env.journal};
watch.discover(*ledger);
auto const line = keylet::line(alice, gw, USD.currency).key;
BEAST_EXPECT(watch.isWatched(line));
// A whole map needs nothing.
BEAST_EXPECT(watch.missingNodes(full, 100).empty());
// A map holding just the root, in a family of its own so that
// nothing resolves from a shared cache or store: what a lite node
// holds after the root reply. Feed it what a peer would answer, one
// round per level, until every watched key reads.
tests::TestNodeFamily family{env.journal};
SHAMap partial{SHAMapType::STATE, family};
partial.setSynching();
{
Serializer s;
full.serializeRoot(s);
BEAST_EXPECT(partial.addRootNode(full.getHash(), s.slice(), nullptr)
.isGood());
}
int rounds = 0;
std::size_t held = 1; // the root
for (; rounds < 32; ++rounds)
{
auto const missing = watch.missingNodes(partial, 100);
if (missing.empty())
break;
held += missing.size();
for (auto const& [id, hash] : missing)
{
std::vector<std::pair<SHAMapNodeID, Blob>> data;
full.getNodeFat(id, data, false, 0);
if (!BEAST_EXPECT(!data.empty()))
return;
BEAST_EXPECT(
partial.addKnownNode(id, makeSlice(data[0].second), nullptr)
.isGood());
}
}
// Converged, in no more rounds than the map is deep.
BEAST_EXPECT(rounds > 0 && rounds < 32);
BEAST_EXPECT(watch.missingNodes(partial, 100).empty());
BEAST_EXPECT(partial.getHash() == full.getHash());
std::optional<std::pair<SHAMapNodeID, uint256>> gap;
BEAST_EXPECT(partial.peekItemPartial(line, gap) && !gap);
BEAST_EXPECT(
partial.peekItemPartial(keylet::account(alice).key, gap) && !gap);
BEAST_EXPECT(partial.peekItemPartial(keylet::fees().key, gap) && !gap);
// bob was never asked for: his path stops at a node not held.
BEAST_EXPECT(
!partial.peekItemPartial(keylet::account(bob).key, gap) && gap);
// And the partial map is a fraction of the whole one. (visitNodes()
// cannot be asked of the partial map: it dereferences every child.)
std::size_t whole = 0;
full.visitNodes([&](SHAMapTreeNode&) {
++whole;
return true;
});
BEAST_EXPECT(held < whole);
}
public:
void
run() override
{
testConfigLines();
testWatchSet();
testDiscover();
testPartialSync();
}
};
BEAST_DEFINE_TESTSUITE(LiteWatch, app, ripple);
} // namespace test
} // namespace ripple
-32
View File
@@ -133,32 +133,6 @@ private:
void
trigger(std::shared_ptr<Peer> const&, TriggerReason);
/** The [lite_mode] acquire: header, whole transaction map, then only
the state nodes on the paths to the watched keys, which the
transactions just fetched may have added to. Called by trigger()
once the header is held, with mtx_ locked and tmGL prepared. */
void
liteTrigger(
ScopedLockType& sl,
std::shared_ptr<Peer> const& peer,
TriggerReason reason,
protocol::TMGetLedger& tmGL);
/** The state nodes still needed to read every watched key. */
std::vector<std::pair<SHAMapNodeID, uint256>>
liteMissingStateNodes(std::size_t max) const;
/** Ask peers for the nodes the early manifest read needs, if any.
@return true if a request was sent, and so the caller should return;
false if the manifests are read, being read, not wanted, or
the nodes are all recently asked for */
bool
earlyManifestSync(
ScopedLockType& sl,
std::shared_ptr<Peer> const& peer,
TriggerReason reason,
protocol::TMGetLedger& tmGL);
std::vector<neededHash_t>
getNeededHashes();
@@ -235,12 +209,6 @@ private:
*/
std::atomic<bool> mManifestsSynced{false};
/** [lite_mode]: the transactions of this ledger have been read for
objects the watch set should grow by. Done once, before the state
paths are requested, so that an object created in this ledger is
fetched from this ledger. */
bool mLiteDiscovered{false};
bool mHaveHeader;
bool mHaveState;
bool mHaveTransactions;
-5
View File
@@ -447,11 +447,6 @@ private:
// ledgers are retained in memory (see mRetainedLedgers).
bool const nullBackend_;
// [lite_mode]: ledgers hold only the state paths to the watched keys.
// They are never marked full, never backfilled, and the validated
// ledger doubles as the closed ledger since no consensus round runs.
bool const liteMode_;
std::uint32_t const ledger_fetch_size_;
TaggedCache<uint256, Blob> fetch_packs_;
-189
View File
@@ -1,189 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_LEDGER_LITEWATCH_H_INCLUDED
#define RIPPLE_APP_LEDGER_LITEWATCH_H_INCLUDED
#include <xrpld/shamap/SHAMap.h>
#include <xrpld/shamap/SHAMapNodeID.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <cstddef>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace ripple {
class Config;
class ReadView;
/** The set of ledger objects a lite node tracks.
A node in [lite_mode] does not hold a ledger's state map. For each
validated ledger it fetches the header, the whole transaction map (small,
and needed to answer `tx` and account subscriptions), and only the state
map nodes on the paths from the root to the keys in this set. Everything
else about the ledger stays on the network.
The set has three tiers:
- System keys every ledger needs to be usable at all: the fee settings,
the amendments object, the skip list and the negative UNL. Always
present, never removed.
- Configured keys from [lite_watch] and the lite_watch RPC: account
roots, raw keylets and trust lines. Removed only by lite_unwatch.
- Discovered keys: objects that transactions touching a watched account
created or modified (a trust line opened to it, an escrow it is the
destination of, hook state its hooks wrote). Added by discover() as
each ledger's transactions arrive, removed again when the object is
deleted.
All methods are thread safe. The walk over the state map in
missingNodes() reads a map that InboundLedger is still adding nodes to,
which is what SHAMap::getMissingNodes() already does.
*/
class LiteWatch
{
public:
enum class Tier { system, configured, discovered };
struct Entry
{
uint256 key;
Tier tier;
/** Why it is watched, for lite_status: "account rXXX", "system
fees", "created by <txid>", ... */
std::string reason;
};
/** Most keys the set will hold, system keys included. Discovery stops
adding at this point rather than let one busy issuer account drag a
Raspberry Pi into fetching its whole owner tree. */
static constexpr std::size_t maxKeys = 16384;
LiteWatch(Config const& config, beast::Journal journal);
/** True when the server runs in [lite_mode]. */
bool
enabled() const
{
return enabled_;
}
/** Watch an account: its AccountRoot and Hook object, plus discovery of
the objects its transactions touch. Returns false if already watched
or full. */
bool
watchAccount(AccountID const& id, Tier tier = Tier::configured);
/** Watch one ledger object by key. */
bool
watchKey(
uint256 const& key,
Tier tier = Tier::configured,
std::string reason = {});
/** Stop watching a key. System keys cannot be removed. Returns true if
something was removed. */
bool
unwatchKey(uint256 const& key);
/** Stop watching an account and its root. Objects discovered through it
are dropped as well; configured keys are kept. */
bool
unwatchAccount(AccountID const& id);
bool
isWatched(uint256 const& key) const;
bool
isWatchedAccount(AccountID const& id) const;
/** Every key currently watched. */
std::vector<uint256>
keys() const;
/** Every entry, for lite_status. */
std::vector<Entry>
entries() const;
std::size_t
size() const;
/** The nodes a partly held state map still needs to read every watched
key. One entry per node, however many keys it blocks; at most `max`.
Empty once every watched key can be read (or shown absent).
@param stateMap State map with at least its root node
@param max Most nodes to report
*/
std::vector<std::pair<SHAMapNodeID, uint256>>
missingNodes(SHAMap const& stateMap, std::size_t max) const;
/** Learn from one validated transaction and its metadata which objects
are now of interest.
- An affected node any of whose account fields name a watched account
(Account, Owner, Destination, Issuer, the two limits of a trust
line, ...) is watched from now on.
- If the transaction was about a watched account (sent by it, ran a
hook of it, or wrote an object naming it), every node it created
is watched too: a new trust line, escrow, check, offer, URIToken
or hook state belonging to the account is almost always among
them, and a transaction creates few objects.
- A deleted node that was only discovered is forgotten.
@return number of keys added
*/
std::size_t
discover(STTx const& tx, STObject const& meta, std::uint32_t ledgerSeq);
/** discover() over every transaction of a ledger whose transaction map
is complete. Reading the state map is not required. */
std::size_t
discover(ReadView const& ledger);
Json::Value
getJson() const;
private:
bool
addLocked(uint256 const& key, Tier tier, std::string reason);
bool const enabled_;
bool const discoverEnabled_;
beast::Journal j_;
mutable std::mutex mutex_;
hash_map<uint256, Entry> keys_;
hash_set<AccountID> accounts_;
};
} // namespace ripple
#endif
+44 -242
View File
@@ -21,7 +21,6 @@
#include <xrpld/app/ledger/InboundLedger.h>
#include <xrpld/app/ledger/InboundLedgers.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/ledger/LiteWatch.h>
#include <xrpld/app/ledger/ManifestSync.h>
#include <xrpld/app/ledger/TransactionStateSF.h>
#include <xrpld/app/main/Application.h>
@@ -245,33 +244,10 @@ InboundLedger::neededTxHashes(int max, SHAMapSyncFilter* filter) const
std::vector<uint256>
InboundLedger::neededStateHashes(int max, SHAMapSyncFilter* filter) const
{
if (app_.config().liteMode())
{
// Only the paths to the watched keys are wanted, so a walk of the
// whole map (which would also mark subtrees full below) is never
// the right question to ask of it.
std::vector<uint256> ret;
if (mLedger->info().accountHash.isNonZero())
{
if (mLedger->stateMap().getHash().isZero())
ret.push_back(mLedger->info().accountHash);
else
for (auto const& n : liteMissingStateNodes(max))
ret.push_back(n.second);
}
return ret;
}
return neededHashes(
mLedger->info().accountHash, mLedger->stateMap(), max, filter);
}
std::vector<std::pair<SHAMapNodeID, uint256>>
InboundLedger::liteMissingStateNodes(std::size_t max) const
{
return app_.getLiteWatch().missingNodes(mLedger->stateMap(), max);
}
// See how much of the ledger data is stored locally
// Data found in a fetch pack will be stored
void
@@ -662,12 +638,6 @@ InboundLedger::trigger(std::shared_ptr<Peer> const& peer, TriggerReason reason)
else
tmGL.set_querydepth(1);
if (app_.config().liteMode())
{
liteTrigger(sl, peer, reason, tmGL);
return;
}
// Get the state data first because it's the most likely to be useful
// if we wind up abandoning this fetch.
if (mHaveHeader && !mHaveState && !failed_)
@@ -705,8 +675,50 @@ InboundLedger::trigger(std::shared_ptr<Peer> const& peer, TriggerReason reason)
// key as well as through the directory. They are what a node with
// no manifests at all needs, and reading them by key does not
// depend on how long the directory has grown.
if (earlyManifestSync(sl, peer, reason, tmGL))
return;
if (!mManifestsSynced && app_.config().EARLY_MANIFEST_SYNC &&
wantManifests())
{
sl.unlock();
auto listed = listedMasterKeys(app_);
std::vector<uint256> targets;
targets.reserve(listed.size());
for (auto const& pk : listed)
targets.push_back(keylet::manifest(pk).key);
auto nodes = missingManifestNodes(
mLedger->stateMap(),
missingNodesFind,
targets,
manifestSyncPages);
sl.lock();
if (!failed_ && !complete_ && !mHaveState && !mManifestsSynced)
{
if (nodes.empty())
{
syncManifests(std::move(listed), manifestSyncPages);
}
else
{
filterNodes(nodes, reason);
// All recently asked for: fall through to the rest of
// the map rather than wait on them.
if (!nodes.empty())
{
tmGL.set_itype(protocol::liAS_NODE);
for (auto const& id : nodes)
*(tmGL.add_nodeids()) = id.first.getRawString();
JLOG(journal_.trace())
<< "Sending manifest node request ("
<< nodes.size() << ") to "
<< (peer ? "selected peer" : "all peers");
mPeerSet->sendRequest(tmGL, peer);
return;
}
}
}
}
AccountStateSF filter(
mLedger->stateMap().family().db(), app_.getLedgerMaster());
@@ -836,216 +848,6 @@ InboundLedger::trigger(std::shared_ptr<Peer> const& peer, TriggerReason reason)
}
}
bool
InboundLedger::earlyManifestSync(
ScopedLockType& sl,
std::shared_ptr<Peer> const& peer,
TriggerReason reason,
protocol::TMGetLedger& tmGL)
{
if (mManifestsSynced || !app_.config().EARLY_MANIFEST_SYNC ||
!wantManifests())
return false;
sl.unlock();
auto listed = listedMasterKeys(app_);
std::vector<uint256> targets;
targets.reserve(listed.size());
for (auto const& pk : listed)
targets.push_back(keylet::manifest(pk).key);
auto nodes = missingManifestNodes(
mLedger->stateMap(), missingNodesFind, targets, manifestSyncPages);
sl.lock();
// Make sure nothing happened while we released the lock
if (failed_ || complete_ || mHaveState || mManifestsSynced)
return false;
if (nodes.empty())
{
syncManifests(std::move(listed), manifestSyncPages);
return false;
}
filterNodes(nodes, reason);
// All recently asked for: fall through to the rest of the map rather
// than wait on them.
if (nodes.empty())
return false;
tmGL.set_itype(protocol::liAS_NODE);
for (auto const& id : nodes)
*(tmGL.add_nodeids()) = id.first.getRawString();
JLOG(journal_.trace()) << "Sending manifest node request (" << nodes.size()
<< ") to " << (peer ? "selected peer" : "all peers");
mPeerSet->sendRequest(tmGL, peer);
return true;
}
void
InboundLedger::liteTrigger(
ScopedLockType& sl,
std::shared_ptr<Peer> const& peer,
TriggerReason reason,
protocol::TMGetLedger& tmGL)
{
XRPL_ASSERT(
failed_ || (mHaveHeader && mLedger),
"ripple::InboundLedger::liteTrigger : header held");
// Transactions first, and all of them. They are what the watchers are
// waiting to see, and they name the objects this ledger created for the
// watched accounts, which must be known before the state paths are
// asked for if they are to be read from this ledger.
if (!mHaveTransactions && !failed_)
{
if (!mLedger->txMap().isValid())
{
failed_ = true;
}
else if (mLedger->txMap().getHash().isZero())
{
tmGL.set_itype(protocol::liTX_NODE);
*(tmGL.add_nodeids()) = SHAMapNodeID().getRawString();
JLOG(journal_.trace()) << "Lite: sending TX root request to "
<< (peer ? "selected peer" : "all peers");
mPeerSet->sendRequest(tmGL, peer);
return;
}
else
{
TransactionStateSF filter(
mLedger->txMap().family().db(), app_.getLedgerMaster());
auto nodes =
mLedger->txMap().getMissingNodes(missingNodesFind, &filter);
if (nodes.empty())
{
if (!mLedger->txMap().isValid())
failed_ = true;
else
mHaveTransactions = true;
}
else
{
filterNodes(nodes, reason);
if (!nodes.empty())
{
tmGL.set_itype(protocol::liTX_NODE);
for (auto const& n : nodes)
*(tmGL.add_nodeids()) = n.first.getRawString();
JLOG(journal_.trace())
<< "Lite: sending TX node request (" << nodes.size()
<< ") to " << (peer ? "selected peer" : "all peers");
mPeerSet->sendRequest(tmGL, peer);
return;
}
JLOG(journal_.trace()) << "Lite: all TX nodes filtered";
}
}
}
// Grow the watch set by what this ledger's transactions did to the
// watched accounts. Reading the complete transaction map is what
// getMissingNodes() just did, so the lock can be released for it.
if (mHaveTransactions && !mLiteDiscovered && !failed_)
{
mLiteDiscovered = true;
sl.unlock();
auto const added = app_.getLiteWatch().discover(*mLedger);
sl.lock();
if (added != 0)
{
JLOG(journal_.debug()) << "Lite: ledger " << mLedger->info().seq
<< " adds " << added << " watched keys";
}
}
if (mHaveTransactions && !mHaveState && !failed_)
{
if (!mLedger->stateMap().isValid())
{
failed_ = true;
}
else if (mLedger->stateMap().getHash().isZero())
{
tmGL.set_itype(protocol::liAS_NODE);
*tmGL.add_nodeids() = SHAMapNodeID().getRawString();
JLOG(journal_.trace()) << "Lite: sending AS root request to "
<< (peer ? "selected peer" : "all peers");
mPeerSet->sendRequest(tmGL, peer);
return;
}
else
{
// Before the first validated ledger the manifests are what
// decide which validations to believe; they are a few paths
// more, read the same way.
if (earlyManifestSync(sl, peer, reason, tmGL))
return;
// The paths to the watched keys, and nothing else. Unchanged
// paths resolve from the tree node cache, which is keyed by
// hash, so in steady state this costs the nodes near the root
// that every ledger rewrites.
sl.unlock();
auto nodes = liteMissingStateNodes(missingNodesFind);
sl.lock();
// Make sure nothing happened while we released the lock
if (!failed_ && !complete_ && !mHaveState)
{
if (nodes.empty())
{
mHaveState = true;
complete_ = true;
}
else
{
filterNodes(nodes, reason);
if (!nodes.empty())
{
tmGL.set_itype(protocol::liAS_NODE);
for (auto const& id : nodes)
*(tmGL.add_nodeids()) = id.first.getRawString();
JLOG(journal_.trace())
<< "Lite: sending AS path request (" << nodes.size()
<< ") to "
<< (peer ? "selected peer" : "all peers");
mPeerSet->sendRequest(tmGL, peer);
return;
}
JLOG(journal_.trace()) << "Lite: all AS nodes filtered";
}
}
}
}
else if (mHaveTransactions && mHaveState && !failed_)
{
// A ledger with no state root, or one tryDB() found whole.
complete_ = true;
}
if (complete_ || failed_)
{
JLOG(journal_.debug()) << "Lite done:" << (complete_ ? " complete" : "")
<< (failed_ ? " failed " : " ")
<< (mLedger ? mLedger->info().seq : mSeq);
sl.unlock();
done();
}
}
bool
InboundLedger::wantManifests()
{
+3 -34
View File
@@ -114,7 +114,6 @@ LedgerMaster::LedgerMaster(
app_.getSHAMapStore().clampFetchDepth(app_.config().FETCH_DEPTH))
, ledger_history_(app_.config().LEDGER_HISTORY)
, nullBackend_(app_.config().nullBackend())
, liteMode_(app_.config().liteMode())
, ledger_fetch_size_(app_.config().getValueFor(SizedItem::ledgerFetch))
, fetch_packs_(
"FetchPack",
@@ -279,28 +278,6 @@ LedgerMaster::setValidLedger(std::shared_ptr<Ledger const> const& l)
mValidLedger.set(l);
mValidLedgerSign = signTime.time_since_epoch().count();
// A lite node runs no consensus round, so nothing else ever sets a
// closed ledger or moves the open ledger on. The validated ledger is the
// most recent one it holds: it is the closed ledger, and the open ledger
// is an empty view on top of it, so that `current` reads, sequence and
// fee autofill see the watched accounts as they now are. Nothing is
// applied to it: a transaction submitted to a lite node is relayed, not
// applied (NetworkOPsImp::apply).
if (liteMode_)
{
mClosedLedger.set(l);
OrderedTxs retries{uint256{}};
app_.openLedger().accept(
app_,
l->rules(),
l,
OrderedTxs({}),
false,
retries,
tapNONE,
"lite");
}
XRPL_ASSERT(
mValidLedgerSeq || !app_.getMaxDisallowedLedger() ||
l->info().seq + max_ledger_difference_ >
@@ -943,11 +920,7 @@ LedgerMaster::setFullLedger(
"ripple::LedgerMaster::setFullLedger : nonzero ledger state hash");
ledger->setValidated();
// A lite ledger holds only the watched paths: marking it full would make
// the first read of anything else trigger a fresh acquire.
if (!liteMode_)
ledger->setFull();
ledger->setFull();
if (isCurrent)
mLedgerHistory.insert(ledger, true);
@@ -1126,8 +1099,7 @@ LedgerMaster::checkAccept(std::shared_ptr<Ledger const> const& ledger)
<< " validations";
ledger->setValidated();
if (!liteMode_)
ledger->setFull();
ledger->setFull();
setValidLedger(ledger);
JLOG(m_journal.info()) << "checkAccept (" << ledger->info().seq
@@ -2300,10 +2272,7 @@ LedgerMaster::doAdvance(std::unique_lock<std::recursive_mutex>& sl)
auto const pubLedgers = findNewLedgersToPublish(sl);
if (pubLedgers.empty())
{
// A lite node keeps no history: it serves nobody, and every
// ledger it fetched would cost the same paths over again.
if (!standalone_ && !liteMode_ &&
!app_.getFeeTrack().isLoadedLocal() &&
if (!standalone_ && !app_.getFeeTrack().isLoadedLocal() &&
(app_.getJobQueue().getJobCount(jtPUBOLDLEDGER) < 10) &&
(mValidLedgerSeq == mPubLedgerSeq) &&
(getValidatedLedgerAge() < MAX_LEDGER_AGE_ACQUIRE) &&
-443
View File
@@ -1,443 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/ledger/LiteWatch.h>
#include <xrpld/core/Config.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpld/ledger/ReadView.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
LiteWatch::LiteWatch(Config const& config, beast::Journal journal)
: enabled_(config.LITE_MODE)
, discoverEnabled_(config.LITE_DISCOVER)
, j_(journal)
{
if (!enabled_)
return;
// Read by Ledger::setup(), AmendmentTable::doValidatedLedger(),
// hashOfSeq() and LedgerMaster::setValidLedger() on every ledger: without
// them a ledger cannot even be accepted.
addLocked(keylet::fees().key, Tier::system, "system fees");
addLocked(keylet::amendments().key, Tier::system, "system amendments");
addLocked(keylet::skip().key, Tier::system, "system skip list");
addLocked(keylet::negativeUNL().key, Tier::system, "system negative UNL");
for (auto const& line : config.LITE_WATCH)
{
std::string err;
auto const parsed = parseLiteWatchLine(line, err);
if (!parsed)
{
// Config::loadFromString() has already rejected these.
JLOG(j_.warn()) << "[" SECTION_LITE_WATCH "] " << err;
continue;
}
if (parsed->first)
{
accounts_.insert(*parsed->first);
auto const reason = "account " + toBase58(*parsed->first);
addLocked(parsed->second, Tier::configured, reason);
addLocked(
keylet::hook(*parsed->first).key, Tier::configured, reason);
}
else
{
addLocked(parsed->second, Tier::configured, "keylet");
}
}
JLOG(j_.info()) << "Lite mode: watching " << keys_.size() << " keys ("
<< accounts_.size() << " accounts)";
}
bool
LiteWatch::addLocked(uint256 const& key, Tier tier, std::string reason)
{
auto it = keys_.find(key);
if (it != keys_.end())
{
// Promote: a discovered object the operator then asks for by name
// must survive the deletion of whatever it was discovered through.
if (static_cast<int>(tier) < static_cast<int>(it->second.tier))
{
it->second.tier = tier;
it->second.reason = std::move(reason);
}
return false;
}
if (keys_.size() >= maxKeys)
{
JLOG(j_.warn()) << "Lite watch set is full (" << maxKeys
<< " keys); not watching " << key;
return false;
}
keys_.emplace(key, Entry{key, tier, std::move(reason)});
return true;
}
bool
LiteWatch::watchAccount(AccountID const& id, Tier tier)
{
std::lock_guard lock(mutex_);
bool const added = accounts_.insert(id).second;
auto const reason = "account " + toBase58(id);
// The root, and the Hook object that lists the hooks installed on it:
// the base fee of anything the account sends depends on those, and a
// submit autofilling its fee reads them.
bool const root = addLocked(keylet::account(id).key, tier, reason);
bool const hook = addLocked(keylet::hook(id).key, tier, reason);
return root || hook || added;
}
bool
LiteWatch::watchKey(uint256 const& key, Tier tier, std::string reason)
{
std::lock_guard lock(mutex_);
return addLocked(key, tier, reason.empty() ? "keylet" : std::move(reason));
}
bool
LiteWatch::unwatchKey(uint256 const& key)
{
std::lock_guard lock(mutex_);
auto it = keys_.find(key);
if (it == keys_.end() || it->second.tier == Tier::system)
return false;
keys_.erase(it);
return true;
}
bool
LiteWatch::unwatchAccount(AccountID const& id)
{
std::lock_guard lock(mutex_);
bool removed = accounts_.erase(id) != 0;
for (auto const& k : {keylet::account(id).key, keylet::hook(id).key})
{
auto it = keys_.find(k);
if (it != keys_.end() && it->second.tier != Tier::system)
{
keys_.erase(it);
removed = true;
}
}
// Objects only known through this account go with it. Everything an
// operator asked for by key stays.
for (auto i = keys_.begin(); i != keys_.end();)
{
if (i->second.tier == Tier::discovered)
{
i = keys_.erase(i);
removed = true;
}
else
++i;
}
return removed;
}
bool
LiteWatch::isWatched(uint256 const& key) const
{
std::lock_guard lock(mutex_);
return keys_.contains(key);
}
bool
LiteWatch::isWatchedAccount(AccountID const& id) const
{
std::lock_guard lock(mutex_);
return accounts_.contains(id);
}
std::vector<uint256>
LiteWatch::keys() const
{
std::lock_guard lock(mutex_);
std::vector<uint256> ret;
ret.reserve(keys_.size());
for (auto const& [key, entry] : keys_)
ret.push_back(key);
return ret;
}
std::vector<LiteWatch::Entry>
LiteWatch::entries() const
{
std::lock_guard lock(mutex_);
std::vector<Entry> ret;
ret.reserve(keys_.size());
for (auto const& [key, entry] : keys_)
ret.push_back(entry);
return ret;
}
std::size_t
LiteWatch::size() const
{
std::lock_guard lock(mutex_);
return keys_.size();
}
std::vector<std::pair<SHAMapNodeID, uint256>>
LiteWatch::missingNodes(SHAMap const& stateMap, std::size_t max) const
{
std::vector<std::pair<SHAMapNodeID, uint256>> missing;
// Snapshot the keys: the walk reads the map, not the set, and a
// discovery running on another ledger must not wait on it.
auto const wanted = keys();
// By hash: several keys blocked by the same node report it once. Near
// the root that is every key.
hash_set<uint256> reported;
for (auto const& key : wanted)
{
if (missing.size() >= max)
break;
std::optional<std::pair<SHAMapNodeID, uint256>> gap;
stateMap.peekItemPartial(key, gap);
if (gap && reported.insert(gap->second).second)
missing.push_back(*gap);
}
return missing;
}
std::size_t
LiteWatch::discover(
STTx const& tx,
STObject const& meta,
std::uint32_t ledgerSeq)
{
if (!discoverEnabled_ || !meta.isFieldPresent(sfAffectedNodes))
return 0;
std::lock_guard lock(mutex_);
auto const watchedAccount = [&](AccountID const& id) {
return accounts_.contains(id);
};
// Is this transaction about a watched account? Sent by one, run a hook
// of one, or wrote an object that names one. Merely modifying a key
// already watched is not enough: a trust line set against a watched
// issuer touches the issuer's owner directory, and that must not pull
// in every stranger's line.
bool touches = watchedAccount(tx.getAccountID(sfAccount));
if (!touches && meta.isFieldPresent(sfHookExecutions))
{
for (auto const& exec : meta.getFieldArray(sfHookExecutions))
{
if (exec.isFieldPresent(sfHookAccount) &&
watchedAccount(exec.getAccountID(sfHookAccount)))
{
touches = true;
break;
}
}
}
auto const& nodes = meta.getFieldArray(sfAffectedNodes);
// Pass 1: nodes that name a watched account, and whether anything
// watched was touched.
std::vector<uint256> wanted;
std::vector<uint256> deleted;
for (auto const& node : nodes)
{
if (!node.isFieldPresent(sfLedgerIndex))
continue;
auto const index = node.getFieldH256(sfLedgerIndex);
bool const isDeleted = node.getFName() == sfDeletedNode;
if (keys_.contains(index))
{
if (isDeleted)
deleted.push_back(index);
continue;
}
if (isDeleted)
continue;
int const fieldsIndex = node.getFieldIndex(
node.getFName() == sfCreatedNode ? sfNewFields : sfFinalFields);
if (fieldsIndex == -1)
continue;
auto const inner =
dynamic_cast<STObject const*>(&node.peekAtIndex(fieldsIndex));
if (!inner)
continue;
bool const isLine = node.isFieldPresent(sfLedgerEntryType) &&
node.getFieldU16(sfLedgerEntryType) == ltRIPPLE_STATE;
bool names = false;
for (auto const& field : *inner)
{
if (auto const sa = dynamic_cast<STAccount const*>(&field))
{
if (!sa->isDefault() && watchedAccount(sa->value()))
{
names = true;
break;
}
}
else if (
isLine &&
(field.getFName() == sfLowLimit ||
field.getFName() == sfHighLimit))
{
// The two parties of a trust line are the issuers of its
// limits. Not done for offers: an issuer's every order book
// entry is not what "my account" means.
if (auto const lim = dynamic_cast<STAmount const*>(&field);
lim && watchedAccount(lim->getIssuer()))
{
names = true;
break;
}
}
}
if (names)
{
touches = true;
wanted.push_back(index);
}
}
// Pass 2: a transaction that touched something watched created these.
if (touches)
{
for (auto const& node : nodes)
{
if (node.getFName() == sfCreatedNode &&
node.isFieldPresent(sfLedgerIndex))
wanted.push_back(node.getFieldH256(sfLedgerIndex));
}
}
std::size_t added = 0;
std::string const reason = "tx " + to_string(tx.getTransactionID()) +
" ledger " + std::to_string(ledgerSeq);
for (auto const& key : wanted)
if (addLocked(key, Tier::discovered, reason))
++added;
for (auto const& key : deleted)
{
auto it = keys_.find(key);
if (it != keys_.end() && it->second.tier == Tier::discovered)
keys_.erase(it);
}
if (added != 0)
{
JLOG(j_.debug()) << "Lite mode: " << reason << " adds " << added
<< " keys (" << keys_.size() << " watched)";
}
return added;
}
std::size_t
LiteWatch::discover(ReadView const& ledger)
{
if (!discoverEnabled_)
return 0;
std::size_t added = 0;
for (auto const& [tx, meta] : ledger.txs)
{
if (tx && meta)
added += discover(*tx, *meta, ledger.seq());
}
return added;
}
Json::Value
LiteWatch::getJson() const
{
Json::Value ret{Json::objectValue};
ret[jss::enabled] = enabled_;
std::lock_guard lock(mutex_);
ret["discover"] = discoverEnabled_;
ret["key_count"] = static_cast<Json::UInt>(keys_.size());
ret["max_keys"] = static_cast<Json::UInt>(maxKeys);
Json::Value accounts{Json::arrayValue};
for (auto const& id : accounts_)
accounts.append(toBase58(id));
ret[jss::accounts] = std::move(accounts);
std::size_t system = 0, configured = 0, discovered = 0;
Json::Value keys{Json::arrayValue};
for (auto const& [key, entry] : keys_)
{
switch (entry.tier)
{
case Tier::system:
++system;
break;
case Tier::configured:
++configured;
break;
case Tier::discovered:
++discovered;
break;
}
Json::Value k{Json::objectValue};
k[jss::key] = to_string(key);
k["tier"] = entry.tier == Tier::system ? "system"
: entry.tier == Tier::configured ? "configured"
: "discovered";
k["reason"] = entry.reason;
keys.append(std::move(k));
}
ret["system"] = static_cast<Json::UInt>(system);
ret["configured"] = static_cast<Json::UInt>(configured);
ret["discovered"] = static_cast<Json::UInt>(discovered);
ret["keys"] = std::move(keys);
return ret;
}
} // namespace ripple
-15
View File
@@ -24,7 +24,6 @@
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/ledger/LedgerReplayer.h>
#include <xrpld/app/ledger/LedgerToJson.h>
#include <xrpld/app/ledger/LiteWatch.h>
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpld/app/ledger/OrderBookDB.h>
#include <xrpld/app/ledger/PendingSaves.h>
@@ -218,7 +217,6 @@ public:
RCLValidations mValidations;
std::unique_ptr<LoadManager> m_loadManager;
std::unique_ptr<TxQ> txQ_;
std::unique_ptr<LiteWatch> liteWatch_;
ClosureCounter<void, boost::system::error_code const&> waitHandlerCounter_;
boost::asio::steady_timer sweepTimer_;
boost::asio::steady_timer entropyTimer_;
@@ -459,10 +457,6 @@ public:
, txQ_(
std::make_unique<TxQ>(setup_TxQ(*config_), logs_->journal("TxQ")))
, liteWatch_(std::make_unique<LiteWatch>(
*config_,
logs_->journal("LiteWatch")))
, sweepTimer_(get_io_context())
, entropyTimer_(get_io_context())
@@ -817,15 +811,6 @@ public:
return *txQ_;
}
LiteWatch&
getLiteWatch() override
{
XRPL_ASSERT(
liteWatch_,
"ripple::ApplicationImp::getLiteWatch : non-null lite watch");
return *liteWatch_;
}
RelationalDatabase&
getRelationalDatabase() override
{
-3
View File
@@ -79,7 +79,6 @@ class Ledger;
class LedgerMaster;
class LedgerCleaner;
class LedgerReplayer;
class LiteWatch;
class LoadManager;
class ManifestCache;
class ValidatorKeys;
@@ -191,8 +190,6 @@ public:
overlay() = 0;
virtual TxQ&
getTxQ() = 0;
virtual LiteWatch&
getLiteWatch() = 0;
virtual ValidatorList&
validators() = 0;
virtual ValidatorSite&
+2 -58
View File
@@ -1027,30 +1027,6 @@ NetworkOPsImp::processHeartbeatTimer()
<< ", changing to " << strOperatingMode(newMode, true);
}
CLOG(clog.ss()) << ". ";
// A lite node holds no state to close a ledger from, so no
// consensus round ever runs. It tracks the network by the validated
// ledgers it fetches: in step while the last one is fresh.
if (app_.config().liteMode())
{
using namespace std::chrono_literals;
auto const age = m_ledgerMaster.getValidatedLedgerAge();
if (age < 1min)
{
if (mMode < OperatingMode::TRACKING)
setMode(OperatingMode::TRACKING);
}
else if (mMode == OperatingMode::TRACKING)
{
setMode(OperatingMode::CONNECTED);
}
CLOG(clog.ss())
<< "lite mode, validated ledger age " << age.count() << "s. ";
lock.unlock();
bootstrapManifests();
setHeartbeatTimer();
return;
}
}
mConsensus.timerEntry(app_.timeKeeper().closeTime(), clog.ss());
@@ -1590,28 +1566,9 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
batchLock.unlock();
// A lite node cannot apply a transaction: doing so reads the sender's
// root, the destination's, its hooks, directories and whatever else the
// transactor touches, nearly all of which it does not hold. It checks
// what can be checked without state (preflight, done by
// checkValidity() on the way in) and relays the rest to the network,
// reporting terLITE_RELAYED. The outcome is read from the validated
// ledger it turns up in, through `tx` or an account subscription.
bool const lite = app_.config().liteMode();
{
std::unique_lock masterLock{app_.getMasterMutex(), std::defer_lock};
bool changed = false;
if (lite)
{
masterLock.lock();
for (TransactionStatus& e : transactions)
{
e.result = terLITE_RELAYED;
e.applied = false;
}
}
else
{
std::unique_lock ledgerLock{
m_ledgerMaster.peekMutex(), std::defer_lock};
@@ -1699,16 +1656,6 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
JLOG(m_journal.info()) << "Transaction is obsolete";
e.transaction->setStatus(OBSOLETE);
}
else if (e.result == terLITE_RELAYED)
{
// Neither held nor kept: a lite node has nothing to retry
// it against, and LocalTxs::sweep() would read the sender
// on every validated ledger.
JLOG(m_journal.debug())
<< "Transaction relayed by lite node, not applied";
e.transaction->setStatus(HELD);
addLocal = false;
}
else if (e.result == terQUEUED)
{
JLOG(m_journal.debug())
@@ -1756,8 +1703,7 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
if ((e.applied ||
((mMode != OperatingMode::FULL) &&
(e.failType != FailHard::yes) && e.local) ||
(e.result == terQUEUED) ||
(e.result == terLITE_RELAYED && e.local)) &&
(e.result == terQUEUED)) &&
!enforceFailHard)
{
auto const toSkip =
@@ -1789,9 +1735,7 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
}
}
// Not for a lite node: this reads the sender's root, which it
// holds only if the sender is watched.
if (validatedLedgerIndex && !lite)
if (validatedLedgerIndex)
{
auto [fee, accountSeq, availableSeq] =
app_.getTxQ().getTxRequiredFeeAndSeq(
+15 -55
View File
@@ -18,61 +18,21 @@
//==============================================================================
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/app/tx/detail/AMMBid.h>
#include <xrpld/app/tx/detail/AMMClawback.h>
#include <xrpld/app/tx/detail/AMMCreate.h>
#include <xrpld/app/tx/detail/AMMDelete.h>
#include <xrpld/app/tx/detail/AMMDeposit.h>
#include <xrpld/app/tx/detail/AMMVote.h>
#include <xrpld/app/tx/detail/AMMWithdraw.h>
#include <xrpld/app/tx/detail/ApplyContext.h>
#include <xrpld/app/tx/detail/CancelCheck.h>
#include <xrpld/app/tx/detail/CancelOffer.h>
#include <xrpld/app/tx/detail/CashCheck.h>
#include <xrpld/app/tx/detail/Change.h>
#include <xrpld/app/tx/detail/ClaimReward.h>
#include <xrpld/app/tx/detail/Clawback.h>
#include <xrpld/app/tx/detail/CreateCheck.h>
#include <xrpld/app/tx/detail/CreateOffer.h>
#include <xrpld/app/tx/detail/CreateTicket.h>
#include <xrpld/app/tx/detail/Credentials.h>
#include <xrpld/app/tx/detail/Cron.h>
#include <xrpld/app/tx/detail/CronSet.h>
#include <xrpld/app/tx/detail/DID.h>
#include <xrpld/app/tx/detail/DeleteAccount.h>
#include <xrpld/app/tx/detail/DeleteOracle.h>
#include <xrpld/app/tx/detail/DepositPreauth.h>
#include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/app/tx/detail/GenesisMint.h>
#include <xrpld/app/tx/detail/Import.h>
#include <xrpld/app/tx/detail/Invoke.h>
#include <xrpld/app/tx/detail/LedgerStateFix.h>
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
#include <xrpld/app/tx/detail/NFTokenBurn.h>
#include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
#include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
#include <xrpld/app/tx/detail/NFTokenMint.h>
#include <xrpld/app/tx/detail/NFTokenModify.h>
#include <xrpld/app/tx/detail/PayChan.h>
#include <xrpld/app/tx/detail/Payment.h>
#include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
#include <xrpld/app/tx/detail/Remit.h>
#include <xrpld/app/tx/detail/SetAccount.h>
#include <xrpld/app/tx/detail/SetHook.h>
#include <xrpld/app/tx/detail/SetManifest.h>
#include <xrpld/app/tx/detail/SetOracle.h>
#include <xrpld/app/tx/detail/SetRegularKey.h>
#include <xrpld/app/tx/detail/SetRemarks.h>
#include <xrpld/app/tx/detail/SetSignerList.h>
#include <xrpld/app/tx/detail/SetTrust.h>
#include <xrpld/app/tx/detail/URIToken.h>
#include <xrpld/app/tx/detail/XChainBridge.h>
#include <xrpld/app/tx/detail/XahauGenesis.h>
#pragma push_macro("TRANSACTION")
#undef TRANSACTION
// Do nothing
#define TRANSACTION(...)
#define TRANSACTION_INCLUDE 1
#include <xrpl/protocol/detail/transactions.macro>
#undef TRANSACTION
#pragma pop_macro("TRANSACTION")
// DO NOT INCLUDE TRANSACTOR HEADER FILES HERE.
// See the instructions at the top of transactions.macro instead.
#include <xrpl/protocol/TxFormats.h>
#include <stdexcept>
-25
View File
@@ -25,7 +25,6 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SystemParameters.h> // VFALCO Breaks levelization
@@ -196,15 +195,6 @@ public:
// ahead of the rest of its state. See [early_manifest_sync].
bool EARLY_MANIFEST_SYNC = true;
// Lite mode: follow validated ledgers without holding their state maps,
// fetching only the paths to the objects in [lite_watch] (and the ones
// their transactions lead to). Implies [node_db] type=rwdb, no
// pathfinding, no consensus participation and no history fetch. See
// [lite_mode], [lite_watch] and [lite_discover].
bool LITE_MODE = false;
bool LITE_DISCOVER = true;
std::vector<std::string> LITE_WATCH;
// True to ask peers not to relay current IP.
bool PEER_PRIVATE = false;
@@ -383,13 +373,6 @@ public:
return RUN_STANDALONE;
}
/** True when the server runs in [lite_mode]. */
bool
liteMode() const
{
return LITE_MODE;
}
bool
mem_backend() const
{
@@ -462,14 +445,6 @@ public:
FeeSetup
setup_FeeVote(Section const& section);
/** Parse one [lite_watch] line: a base58 account or a 64 hex digit key.
@return the account (if the line was one) and the key to watch, or
nothing with `error` set
*/
std::optional<std::pair<std::optional<AccountID>, uint256>>
parseLiteWatchLine(std::string const& line, std::string& error);
} // namespace ripple
#endif
-3
View File
@@ -61,9 +61,6 @@ struct ConfigSection
#define SECTION_IPS_FIXED "ips_fixed"
#define SECTION_LEDGER_HISTORY "ledger_history"
#define SECTION_LEDGER_REPLAY "ledger_replay"
#define SECTION_LITE_DISCOVER "lite_discover"
#define SECTION_LITE_MODE "lite_mode"
#define SECTION_LITE_WATCH "lite_watch"
#define SECTION_MAX_TRANSACTIONS "max_transactions"
#define SECTION_NETWORK_ID "network_id"
#define SECTION_NETWORK_QUORUM "network_quorum"
-68
View File
@@ -27,7 +27,6 @@
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/json/json_reader.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SystemParameters.h>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
@@ -257,29 +256,6 @@ Config::Config()
{
}
std::optional<std::pair<std::optional<AccountID>, uint256>>
parseLiteWatchLine(std::string const& line, std::string& error)
{
std::string s = line;
boost::algorithm::trim(s);
if (s.empty())
{
error = "empty line";
return std::nullopt;
}
if (auto const id = parseBase58<AccountID>(s))
return std::make_pair(id, keylet::account(*id).key);
uint256 key;
if (s.size() == 64 && key.parseHex(s))
return std::make_pair(std::nullopt, key);
error = "not a base58 account or a 64 hex digit key: " + s;
return std::nullopt;
}
void
Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone)
{
@@ -718,50 +694,6 @@ Config::loadFromString(std::string const& fileContents)
if (getSingleSection(secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_))
PATH_SEARCH_MAX = beast::lexicalCastThrow<int>(strTemp);
if (getSingleSection(secConfig, SECTION_LITE_MODE, strTemp, j_))
LITE_MODE = beast::lexicalCastThrow<bool>(strTemp);
if (LITE_MODE)
{
if (RUN_STANDALONE)
Throw<std::runtime_error>(
"[" SECTION_LITE_MODE
"] cannot be combined with standalone "
"mode: a lite node follows validations and builds nothing");
if (exists(SECTION_VALIDATION_SEED) || exists(SECTION_VALIDATOR_TOKEN))
Throw<std::runtime_error>(
"[" SECTION_LITE_MODE
"] cannot be combined with [" SECTION_VALIDATION_SEED
"] or [" SECTION_VALIDATOR_TOKEN
"]: a lite node does not hold the state it would validate");
if (getSingleSection(secConfig, SECTION_LITE_DISCOVER, strTemp, j_))
LITE_DISCOVER = beast::lexicalCastThrow<bool>(strTemp);
LITE_WATCH = section(SECTION_LITE_WATCH).lines();
for (auto const& line : LITE_WATCH)
{
std::string err;
if (!parseLiteWatchLine(line, err))
Throw<std::runtime_error>("[" SECTION_LITE_WATCH "]: " + err);
}
// A lite node never has the order books or the paths to search.
PATH_SEARCH_MAX = 0;
// Partial state maps are held only by the retained Ledger objects.
// The rwdb null node store is the one that keeps nothing on disk
// and pins a ledger_history window in memory, so default to it, and
// refuse a persistent store that would be fed partial trees.
Section& nodeDb{section(ConfigSection::nodeDatabase())};
if (nodeDb.empty())
nodeDb.set("type", "rwdb");
else if (!nullBackend())
Throw<std::runtime_error>("[" SECTION_LITE_MODE
"] requires [node_db] type=rwdb");
}
if (getSingleSection(secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_))
DEBUG_LOGFILE = strTemp;
-21
View File
@@ -1414,16 +1414,6 @@ PeerImp::handleTransaction(
void
PeerImp::onMessage(std::shared_ptr<protocol::TMGetLedger> const& m)
{
// A lite node holds partial ledgers and builds no transaction sets. It
// never advertises a ledger range, so a request here is a peer guessing
// from its closed-ledger hash; answer with silence rather than with a
// tree that would throw on the first node it does not hold.
if (app_.config().liteMode())
{
JLOG(p_journal_.trace()) << "TMGetLedger: ignored in lite mode";
return;
}
auto badData = [&](std::string const& msg) {
fee_.update(Resource::feeInvalidData, "get_ledger " + msg);
JLOG(p_journal_.warn()) << "TMGetLedger: " << msg;
@@ -2472,17 +2462,6 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
return;
}
// A lite node has no fetch packs to build and no node store to
// look objects up in. Transaction requests (reduce-relay) are still
// served from the transaction master.
if (app_.config().liteMode() &&
packet.type() != protocol::TMGetObjectByHash::otTRANSACTIONS)
{
JLOG(p_journal_.trace())
<< "TMGetObjectByHash: ignored in lite mode";
return;
}
if (packet.type() == protocol::TMGetObjectByHash::otFETCH_PACK)
{
doFetchPack(m);
-3
View File
@@ -133,9 +133,6 @@ Handler const handlerArray[]{
{"ledger_entry", byRef(&doLedgerEntry), Role::USER, NO_CONDITION},
{"ledger_header", byRef(&doLedgerHeader), Role::USER, NO_CONDITION, 1, 1},
{"ledger_request", byRef(&doLedgerRequest), Role::ADMIN, NO_CONDITION},
{"lite_status", byRef(&doLiteStatus), Role::USER, NO_CONDITION},
{"lite_unwatch", byRef(&doLiteUnwatch), Role::ADMIN, NO_CONDITION},
{"lite_watch", byRef(&doLiteWatch), Role::ADMIN, NO_CONDITION},
{"log_level", byRef(&doLogLevel), Role::ADMIN, NO_CONDITION},
{"logrotate", byRef(&doLogRotate), Role::ADMIN, NO_CONDITION},
{"manifest", byRef(&doManifest), Role::USER, NO_CONDITION},
-23
View File
@@ -31,7 +31,6 @@
#include <xrpld/rpc/Role.h>
#include <xrpld/rpc/detail/Handler.h>
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpld/shamap/SHAMapMissingNode.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/json/Object.h>
@@ -206,28 +205,6 @@ callMethod(
perfLog.rpcFinish(name, curId);
return ret;
}
catch (SHAMapMissingNode const& e)
{
perfLog.rpcError(name, curId);
// On a lite node a missing node is the expected answer for any
// object outside the watch set: say so, rather than "internal".
if (context.app.config().liteMode())
{
JLOG(context.j.debug()) << "Lite node asked for an unwatched "
<< "object in " << name << ": " << e.what();
inject_error(rpcLITE_NOT_WATCHED, result);
return rpcLITE_NOT_WATCHED;
}
JLOG(context.j.info()) << "Caught throw: " << e.what();
if (context.loadType == Resource::feeReferenceRPC)
context.loadType = Resource::feeExceptionRPC;
inject_error(rpcINTERNAL, result);
return rpcINTERNAL;
}
catch (std::exception& e)
{
perfLog.rpcError(name, curId);
-6
View File
@@ -81,12 +81,6 @@ doLedgerAccept(RPC::JsonContext&);
Json::Value
doLedgerCleaner(RPC::JsonContext&);
Json::Value
doLiteStatus(RPC::JsonContext&);
Json::Value
doLiteUnwatch(RPC::JsonContext&);
Json::Value
doLiteWatch(RPC::JsonContext&);
Json::Value
doLedgerClosed(RPC::JsonContext&);
Json::Value
doLedgerCurrent(RPC::JsonContext&);
-216
View File
@@ -1,216 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/ledger/LiteWatch.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/core/Config.h>
#include <xrpld/rpc/Context.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
namespace {
/** Collect the keys a lite_watch / lite_unwatch request names.
{
"accounts": ["rXXX", ...], // AccountRoot, plus discovery
"keylets": ["<64 hex>", ...], // any ledger object by key
"lines": [ // trust lines by their parties
{"account": "rXXX", "issuer": "rYYY", "currency": "USD"}, ...
]
}
A single "account" is accepted as well, as in account_info.
*/
struct Wanted
{
std::vector<AccountID> accounts;
std::vector<std::pair<uint256, std::string>> keys;
};
std::optional<Json::Value>
collect(Json::Value const& params, Wanted& wanted)
{
auto const account = [&](Json::Value const& v, AccountID& out) {
if (!v.isString())
return false;
auto const id = parseBase58<AccountID>(v.asString());
if (!id)
return false;
out = *id;
return true;
};
if (params.isMember(jss::account))
{
AccountID id;
if (!account(params[jss::account], id))
return RPC::make_error(rpcACT_MALFORMED);
wanted.accounts.push_back(id);
}
if (params.isMember(jss::accounts))
{
auto const& list = params[jss::accounts];
if (!list.isArray())
return RPC::expected_field_error(jss::accounts, "array");
for (auto const& v : list)
{
AccountID id;
if (!account(v, id))
return RPC::make_error(rpcACT_MALFORMED);
wanted.accounts.push_back(id);
}
}
if (params.isMember("keylets"))
{
auto const& list = params["keylets"];
if (!list.isArray())
return RPC::expected_field_error("keylets", "array");
for (auto const& v : list)
{
uint256 key;
if (!v.isString() || v.asString().size() != 64 ||
!key.parseHex(v.asString()))
return RPC::make_param_error(
"keylets entries must be 64 hex digits");
wanted.keys.emplace_back(key, "keylet");
}
}
if (params.isMember(jss::lines))
{
auto const& list = params[jss::lines];
if (!list.isArray())
return RPC::expected_field_error(jss::lines, "array");
for (auto const& v : list)
{
AccountID a, issuer;
Currency currency;
if (!v.isObject() || !account(v[jss::account], a) ||
!account(v[jss::issuer], issuer) ||
!v[jss::currency].isString() ||
!to_currency(currency, v[jss::currency].asString()))
return RPC::make_param_error(
"lines entries need account, issuer and currency");
wanted.keys.emplace_back(
keylet::line(a, issuer, currency).key,
"line " + toBase58(a) + " " + to_string(currency) + " " +
toBase58(issuer));
}
}
if (wanted.accounts.empty() && wanted.keys.empty())
return RPC::make_param_error(
"nothing to watch: give accounts, keylets or lines");
return std::nullopt;
}
Json::Value
notLite()
{
return RPC::make_error(
rpcNOT_SUPPORTED, "This server is not running in [lite_mode].");
}
} // namespace
Json::Value
doLiteWatch(RPC::JsonContext& context)
{
auto& watch = context.app.getLiteWatch();
if (!watch.enabled())
return notLite();
Wanted wanted;
if (auto err = collect(context.params, wanted))
return *err;
Json::Value ret{Json::objectValue};
Json::Value added{Json::arrayValue};
for (auto const& id : wanted.accounts)
if (watch.watchAccount(id))
added.append(toBase58(id));
for (auto const& [key, reason] : wanted.keys)
if (watch.watchKey(key, LiteWatch::Tier::configured, reason))
added.append(to_string(key));
ret["added"] = std::move(added);
ret["key_count"] = static_cast<Json::UInt>(watch.size());
// Nothing is fetched retroactively: the new keys are read from the
// next validated ledger, which is a few seconds away.
if (auto const valid = context.ledgerMaster.getValidatedLedger())
ret["effective_from_ledger"] = valid->info().seq + 1;
return ret;
}
Json::Value
doLiteUnwatch(RPC::JsonContext& context)
{
auto& watch = context.app.getLiteWatch();
if (!watch.enabled())
return notLite();
Wanted wanted;
if (auto err = collect(context.params, wanted))
return *err;
Json::Value ret{Json::objectValue};
Json::Value removed{Json::arrayValue};
for (auto const& id : wanted.accounts)
if (watch.unwatchAccount(id))
removed.append(toBase58(id));
for (auto const& [key, reason] : wanted.keys)
if (watch.unwatchKey(key))
removed.append(to_string(key));
ret["removed"] = std::move(removed);
ret["key_count"] = static_cast<Json::UInt>(watch.size());
return ret;
}
Json::Value
doLiteStatus(RPC::JsonContext& context)
{
auto& watch = context.app.getLiteWatch();
auto ret = watch.getJson();
if (!context.params.isMember("verbose") ||
!context.params["verbose"].asBool())
ret.removeMember("keys");
if (auto const valid = context.ledgerMaster.getValidatedLedger())
{
ret[jss::ledger_index] = valid->info().seq;
ret[jss::ledger_hash] = to_string(valid->info().hash);
}
return ret;
}
} // namespace ripple
-11
View File
@@ -20,7 +20,6 @@
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/app/main/Tuning.h>
#include <xrpld/core/Config.h>
#include <xrpld/shamap/NodeFamily.h>
#include <sstream>
@@ -70,16 +69,6 @@ NodeFamily::reset()
void
NodeFamily::missingNodeAcquireBySeq(std::uint32_t seq, uint256 const& nodeHash)
{
// A lite ledger is missing almost everything by design, and is as
// complete as it will get: acquiring it again would fetch the same
// paths. Reads of unwatched keys surface as SHAMapMissingNode instead.
if (app_.config().liteMode())
{
JLOG(j_.trace()) << "Lite mode: unwatched node " << nodeHash << " in "
<< seq;
return;
}
JLOG(j_.error()) << "Missing node in " << seq;
std::unique_lock<std::mutex> lock(maxSeqMutex_);
if (maxSeq_ == 0)