mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-21 17:47:09 +00:00
Compare commits
245 Commits
develop
...
ximinez/on
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd4eca22b7 | ||
|
|
1116dbfd0e | ||
|
|
258d5e1f1f | ||
|
|
4270367efc | ||
|
|
fad34e852d | ||
|
|
f5b4ac358f | ||
|
|
875bcc530e | ||
|
|
e75f5b101b | ||
|
|
95f74d61b2 | ||
|
|
4e12b787be | ||
|
|
8b6c80027b | ||
|
|
ace73678cf | ||
|
|
12bbba00b0 | ||
|
|
2affb66f50 | ||
|
|
d09e785c39 | ||
|
|
97fdf310f9 | ||
|
|
d3ce76825c | ||
|
|
1e7e274727 | ||
|
|
b272d71cc7 | ||
|
|
21268d9c36 | ||
|
|
932e22df7d | ||
|
|
652a7cd225 | ||
|
|
4a224dbfa4 | ||
|
|
d2a5981f87 | ||
|
|
a977836630 | ||
|
|
bf075200bb | ||
|
|
ee49e76a16 | ||
|
|
c1318990f3 | ||
|
|
69128294f8 | ||
|
|
149e884ebc | ||
|
|
d27353225c | ||
|
|
13a0f77eb5 | ||
|
|
549c093398 | ||
|
|
fb66ca7a2e | ||
|
|
e2af73b0a0 | ||
|
|
14c3c9a256 | ||
|
|
4b6851a287 | ||
|
|
d182673d24 | ||
|
|
bea609d805 | ||
|
|
56eeb20bc8 | ||
|
|
e293a3d918 | ||
|
|
3dff580d39 | ||
|
|
487f5a0fd3 | ||
|
|
d8134f98e9 | ||
|
|
450a623d4b | ||
|
|
9e517be4ce | ||
|
|
b8370438fb | ||
|
|
ecb5604d3b | ||
|
|
c4527e7b0f | ||
|
|
778e2b3ce8 | ||
|
|
295d03aec8 | ||
|
|
9262c2e624 | ||
|
|
b385a41aa5 | ||
|
|
4eb9726097 | ||
|
|
28a38ef1cc | ||
|
|
39f9380b2b | ||
|
|
239fcaceb4 | ||
|
|
8ab86f009e | ||
|
|
d4b58a74f4 | ||
|
|
ff900591ae | ||
|
|
a5b7471af6 | ||
|
|
e0734986dd | ||
|
|
8440f479e5 | ||
|
|
ac390622e0 | ||
|
|
5d807f0d6d | ||
|
|
b93294f26b | ||
|
|
ef95ace0f9 | ||
|
|
fc58bf6edf | ||
|
|
348555d5ba | ||
|
|
302802e42d | ||
|
|
5d881f87a3 | ||
|
|
a8a8035b32 | ||
|
|
45af14231f | ||
|
|
380bf274d0 | ||
|
|
460ec5eeea | ||
|
|
14be8ca4ea | ||
|
|
948264d44c | ||
|
|
9c816b2043 | ||
|
|
f68402acd1 | ||
|
|
5358e25eaa | ||
|
|
a34d1e5537 | ||
|
|
78a122943d | ||
|
|
06135e7203 | ||
|
|
f20425fa4f | ||
|
|
3e94546acc | ||
|
|
3b088ed0dc | ||
|
|
26182ed52e | ||
|
|
de2a3e10f5 | ||
|
|
e17f8554fc | ||
|
|
386a7192ba | ||
|
|
12cc6e424d | ||
|
|
c9deecf1b7 | ||
|
|
ddd1b49f38 | ||
|
|
c1b2a24005 | ||
|
|
86d88eca31 | ||
|
|
ef09eaea00 | ||
|
|
c504cfb291 | ||
|
|
0c217dfa2b | ||
|
|
b0198d2566 | ||
|
|
7eee8ca802 | ||
|
|
2a079a0154 | ||
|
|
40989c1178 | ||
|
|
addc831eb3 | ||
|
|
b4efc6d116 | ||
|
|
125d075d6e | ||
|
|
370a775479 | ||
|
|
1a2ee706eb | ||
|
|
2a981357ba | ||
|
|
1ae475e724 | ||
|
|
a3e9401fbc | ||
|
|
9091469f9e | ||
|
|
17fa54f1f9 | ||
|
|
8fb5347c2d | ||
|
|
6739bf998f | ||
|
|
6eea38ba67 | ||
|
|
e9cf88b359 | ||
|
|
645b203476 | ||
|
|
be2aff1f4c | ||
|
|
56ed237e82 | ||
|
|
fd7b0fd135 | ||
|
|
e700994891 | ||
|
|
c76f7029ac | ||
|
|
d535c5fb2a | ||
|
|
54f860463e | ||
|
|
950434b8ff | ||
|
|
ee365e876d | ||
|
|
c6c59834b9 | ||
|
|
63b47914b8 | ||
|
|
9e02e5be2e | ||
|
|
093cd70fa1 | ||
|
|
376d65a483 | ||
|
|
a0d9a2458e | ||
|
|
456f639cf7 | ||
|
|
2c559ec2f3 | ||
|
|
619c81f463 | ||
|
|
f1490df960 | ||
|
|
7bdf74de98 | ||
|
|
1743d6fb98 | ||
|
|
ca7a5bb926 | ||
|
|
ce8b1a3f1e | ||
|
|
486fa75a10 | ||
|
|
f8d68cd3d3 | ||
|
|
ef7a3f5606 | ||
|
|
4f84ed7490 | ||
|
|
d534103131 | ||
|
|
82dff3c2ce | ||
|
|
30d73eb5ba | ||
|
|
1b2754bac2 | ||
|
|
cf80cafc75 | ||
|
|
b8897d51de | ||
|
|
3ff25eeb65 | ||
|
|
2bbfc4e786 | ||
|
|
2b1eb052e6 | ||
|
|
360e214e54 | ||
|
|
2618afed94 | ||
|
|
698ba2c788 | ||
|
|
b614e99588 | ||
|
|
fe8e4af2fa | ||
|
|
0a897f1528 | ||
|
|
cf8a3f5779 | ||
|
|
db39a39868 | ||
|
|
37a03d28c2 | ||
|
|
19d275425a | ||
|
|
88e9045602 | ||
|
|
5adbc536b6 | ||
|
|
e27af94ba9 | ||
|
|
43fe1e7e9c | ||
|
|
f456a858c8 | ||
|
|
084c3aa88e | ||
|
|
34f9b63921 | ||
|
|
bd3de79817 | ||
|
|
304eee2259 | ||
|
|
9e729b7f59 | ||
|
|
dd141468c4 | ||
|
|
933147c21f | ||
|
|
9201a4f591 | ||
|
|
5adb1e9b8b | ||
|
|
4df84d7988 | ||
|
|
cd87c0968b | ||
|
|
8a8e7c90bf | ||
|
|
e806069065 | ||
|
|
ce948cbec0 | ||
|
|
6ed34b3294 | ||
|
|
7161a235ca | ||
|
|
71463810de | ||
|
|
e997219a85 | ||
|
|
895cc13fa6 | ||
|
|
8d3c3ca29a | ||
|
|
9829553807 | ||
|
|
e551f9731a | ||
|
|
fd827bf58b | ||
|
|
5a3baba34d | ||
|
|
c78f5b160f | ||
|
|
485f78761a | ||
|
|
23cd2f7b21 | ||
|
|
5753266c43 | ||
|
|
4722d2607d | ||
|
|
85b5b4f855 | ||
|
|
a16f492f0f | ||
|
|
3633dc632c | ||
|
|
b3b30c3a86 | ||
|
|
c78a7684f4 | ||
|
|
cf83d92630 | ||
|
|
a56b1274d8 | ||
|
|
ae4bdd0492 | ||
|
|
e90102dd3b | ||
|
|
71f0e8db3d | ||
|
|
638929373a | ||
|
|
8440654377 | ||
|
|
9fa66c4741 | ||
|
|
38a9235145 | ||
|
|
c7a3cc9108 | ||
|
|
248337908d | ||
|
|
3d003619fd | ||
|
|
f163dca12c | ||
|
|
6e0ce458e5 | ||
|
|
5fae8480f1 | ||
|
|
e6587d374a | ||
|
|
376cc404e0 | ||
|
|
9898ca638f | ||
|
|
34b46d8f7c | ||
|
|
fe7d0798a7 | ||
|
|
0cecc09d71 | ||
|
|
e091d55561 | ||
|
|
69cf18158b | ||
|
|
6513c53817 | ||
|
|
e13baa58a5 | ||
|
|
951056fe9b | ||
|
|
67700ea6bd | ||
|
|
e5442cf3f1 | ||
|
|
da68076f04 | ||
|
|
b24116a118 | ||
|
|
f67398c6bf | ||
|
|
43d3eb1a24 | ||
|
|
0993315ed5 | ||
|
|
0bc383ada9 | ||
|
|
1841ceca43 | ||
|
|
2714cebabd | ||
|
|
e184db4ce2 | ||
|
|
ac6dc6943c | ||
|
|
ddd53806df | ||
|
|
e629a1f70e | ||
|
|
68076d969c | ||
|
|
d3009d3e1c | ||
|
|
54f7f3c894 |
@@ -1059,10 +1059,11 @@
|
||||
# The online delete process checks periodically
|
||||
# that xrpld is still in sync with the network,
|
||||
# and that the validated ledger is less than
|
||||
# 'age_threshold_seconds' old. If not, then continue
|
||||
# 'age_threshold_seconds' old, and that all
|
||||
# recent ledgers are available. If not, then continue
|
||||
# sleeping for this number of seconds and
|
||||
# checking until healthy.
|
||||
# Default is 5.
|
||||
# Default is 2.
|
||||
#
|
||||
# Notes:
|
||||
# The 'node_db' entry configures the primary, persistent storage.
|
||||
|
||||
@@ -23,9 +23,13 @@ checkTxPermission(SLE::const_ref delegate, STTx const& tx);
|
||||
* @param delegate The delegate account.
|
||||
* @param type Used to determine which granted granular permissions to load,
|
||||
* based on the transaction type.
|
||||
* @return the granted granular permissions tied to the transaction type.
|
||||
* @param granularPermissions Granted granular permissions tied to the
|
||||
* transaction type.
|
||||
*/
|
||||
std::unordered_set<GranularPermissionType>
|
||||
getGranularPermission(SLE::const_ref delegate, TxType const& type);
|
||||
void
|
||||
loadGranularPermission(
|
||||
SLE::const_ref delegate,
|
||||
TxType const& type,
|
||||
std::unordered_set<GranularPermissionType>& granularPermissions);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -7,13 +7,8 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class STTx;
|
||||
|
||||
/**
|
||||
* We have both transaction type permissions and granular type permissions.
|
||||
* Since we will reuse the TransactionFormats to parse the Transaction
|
||||
@@ -24,15 +19,15 @@ class STTx;
|
||||
// Macro-generated, complex
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum GranularPermissionType : std::uint32_t {
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define GRANULAR_PERMISSION(name, txType, value, ...) name = (value),
|
||||
#define PERMISSION(type, txType, value) type = (value),
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
// Injected bare enumerators (xrpl::delegable / xrpl::notDelegable) are required by preprocessor
|
||||
@@ -45,30 +40,15 @@ class Permission
|
||||
private:
|
||||
Permission();
|
||||
|
||||
struct GranularPermissionEntry
|
||||
{
|
||||
std::string name;
|
||||
TxType txType;
|
||||
std::uint32_t permittedFlags;
|
||||
SOTemplate permittedFields;
|
||||
std::unordered_map<std::uint16_t, uint256> txFeatureMap_;
|
||||
|
||||
GranularPermissionEntry(
|
||||
std::string name,
|
||||
TxType txType,
|
||||
std::uint32_t permittedFlags,
|
||||
std::vector<SOElement> fields);
|
||||
};
|
||||
std::unordered_map<std::uint16_t, Delegation> delegableTx_;
|
||||
|
||||
struct TxDelegationEntry
|
||||
{
|
||||
uint256 amendment;
|
||||
Delegation delegable{NotDelegable};
|
||||
};
|
||||
std::unordered_map<std::string, GranularPermissionType> granularPermissionMap_;
|
||||
|
||||
std::unordered_set<TxType> granularTxTypes_;
|
||||
std::unordered_map<TxType, TxDelegationEntry> txDelegationMap_;
|
||||
std::unordered_map<std::string, GranularPermissionType> granularPermissionsByName_;
|
||||
std::unordered_map<GranularPermissionType, GranularPermissionEntry> granularPermissions_;
|
||||
std::unordered_map<GranularPermissionType, std::string> granularNameMap_;
|
||||
|
||||
std::unordered_map<GranularPermissionType, TxType> granularTxTypeMap_;
|
||||
|
||||
public:
|
||||
static Permission const&
|
||||
@@ -79,52 +59,30 @@ public:
|
||||
operator=(Permission const&) = delete;
|
||||
|
||||
[[nodiscard]] std::optional<std::string>
|
||||
getPermissionName(std::uint32_t value) const;
|
||||
getPermissionName(std::uint32_t const value) const;
|
||||
|
||||
[[nodiscard]] std::optional<std::uint32_t>
|
||||
getGranularValue(std::string const& name) const;
|
||||
|
||||
[[nodiscard]] std::optional<std::string>
|
||||
getGranularName(GranularPermissionType value) const;
|
||||
getGranularName(GranularPermissionType const& value) const;
|
||||
|
||||
[[nodiscard]] std::optional<TxType>
|
||||
getGranularTxType(GranularPermissionType gpType) const;
|
||||
getGranularTxType(GranularPermissionType const& gpType) const;
|
||||
|
||||
// Returns a reference to avoid copying uint256 - 32 bytes. std::optional
|
||||
// cannot hold references directly, so std::reference_wrapper is used.
|
||||
[[nodiscard]] std::optional<std::reference_wrapper<uint256 const>>
|
||||
getTxFeature(TxType txType) const;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDelegable(std::uint32_t permissionValue, Rules const& rules) const;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasGranularPermissions(TxType txType) const;
|
||||
isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const;
|
||||
|
||||
// for tx level permission, permission value is equal to tx type plus one
|
||||
[[nodiscard]] static uint32_t
|
||||
txToPermissionType(TxType type);
|
||||
static uint32_t
|
||||
txToPermissionType(TxType const& type);
|
||||
|
||||
// tx type value is permission value minus one
|
||||
[[nodiscard]] static TxType
|
||||
permissionToTxType(std::uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Verifies a delegated transaction against its granular permission template.
|
||||
*
|
||||
* @note WARNING: Do not move this check before standard transaction-level
|
||||
* format checks, which is in preclaim. This function assumes the transaction's
|
||||
* base structural integrity (fees, sequence, signatures) has already been
|
||||
* validated.
|
||||
*
|
||||
* @param tx The transaction to verify.
|
||||
* @param heldPermissions The granular permissions that the sender hold.
|
||||
* @return true if the transaction fields and flags comply with the granular template.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
checkGranularSandbox(
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldPermissions) const;
|
||||
static TxType
|
||||
permissionToTxType(uint32_t const& value);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -21,7 +21,7 @@ XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultN
|
||||
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocol, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegationV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegationV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (DirectoryLimit, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (IncludeKeyletFields, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicMPT, Supported::No, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -1,74 +1,49 @@
|
||||
#if !defined(GRANULAR_PERMISSION)
|
||||
#error "undefined macro: GRANULAR_PERMISSION"
|
||||
#if !defined(PERMISSION)
|
||||
#error "undefined macro: PERMISSION"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* GRANULAR_PERMISSION(name, txType, value, allowedFlags, allowedFields)
|
||||
* PERMISSION(name, type, txType, value)
|
||||
*
|
||||
* Defines a granular permission:
|
||||
* name: the granular permission name.
|
||||
* txType: the corresponding TxType for this permission.
|
||||
* value: the uint32 numeric value for the enum type.
|
||||
* allowedFlags: transaction flags permitted under this permission.
|
||||
* allowedFields: transaction fields permitted under this permission.
|
||||
* This macro defines a permission:
|
||||
* name: the name of the permission.
|
||||
* type: the GranularPermissionType enum.
|
||||
* txType: the corresponding TxType for this permission.
|
||||
* value: the uint32 numeric value for the enum type.
|
||||
*/
|
||||
|
||||
/** Grants the ability to authorize a trustline. */
|
||||
GRANULAR_PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537, tfUniversal | tfSetfAuth,
|
||||
({{sfLimitAmount, SoeRequired}}))
|
||||
/** This permission grants the delegated account the ability to authorize a trustline. */
|
||||
PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537)
|
||||
|
||||
/** Grants the ability to freeze a trustline. */
|
||||
GRANULAR_PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538, tfUniversal | tfSetFreeze,
|
||||
({{sfLimitAmount, SoeRequired}}))
|
||||
/** This permission grants the delegated account the ability to freeze a trustline. */
|
||||
PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538)
|
||||
|
||||
/** Grants the ability to unfreeze a trustline. */
|
||||
GRANULAR_PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539, tfUniversal | tfClearFreeze,
|
||||
({{sfLimitAmount, SoeRequired}}))
|
||||
/** This permission grants the delegated account the ability to unfreeze a trustline. */
|
||||
PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539)
|
||||
|
||||
/** Grants the ability to set Domain. */
|
||||
GRANULAR_PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540, tfUniversal,
|
||||
({{sfDomain, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to set Domain. */
|
||||
PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540)
|
||||
|
||||
/** Grants the ability to set EmailHash. */
|
||||
GRANULAR_PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541, tfUniversal,
|
||||
({{sfEmailHash, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to set EmailHashSet. */
|
||||
PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541)
|
||||
|
||||
/** Grants the ability to set MessageKey. */
|
||||
GRANULAR_PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542, tfUniversal,
|
||||
({{sfMessageKey, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to set MessageKey. */
|
||||
PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542)
|
||||
|
||||
/** Grants the ability to set TransferRate. */
|
||||
GRANULAR_PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543, tfUniversal,
|
||||
({{sfTransferRate, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to set TransferRate. */
|
||||
PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543)
|
||||
|
||||
/** Grants the ability to set TickSize. */
|
||||
GRANULAR_PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544, tfUniversal,
|
||||
({{sfTickSize, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to set TickSize. */
|
||||
PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544)
|
||||
|
||||
/** Grants the ability to mint payment (sending account is the issuer). Cross-currency payments are disallowed. */
|
||||
GRANULAR_PERMISSION(PaymentMint, ttPAYMENT, 65545, tfUniversal,
|
||||
({{sfDestination, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfSendMax, SoeOptional},
|
||||
{sfInvoiceID, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to mint payment, which means sending a payment for a currency where the sending account is the issuer. */
|
||||
PERMISSION(PaymentMint, ttPAYMENT, 65545)
|
||||
|
||||
/** Grants the ability to burn payment (destination account is the issuer). Cross-currency payments are disallowed. */
|
||||
GRANULAR_PERMISSION(PaymentBurn, ttPAYMENT, 65546, tfUniversal,
|
||||
({{sfDestination, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfSendMax, SoeOptional},
|
||||
{sfInvoiceID, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to burn payment, which means sending a payment for a currency where the destination account is the issuer */
|
||||
PERMISSION(PaymentBurn, ttPAYMENT, 65546)
|
||||
|
||||
/** Grants the ability to lock an MPToken. */
|
||||
GRANULAR_PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547, tfUniversal | tfMPTLock,
|
||||
({{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfHolder, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to lock MPToken. */
|
||||
PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547)
|
||||
|
||||
/** Grants the ability to unlock an MPToken. */
|
||||
GRANULAR_PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548, tfUniversal | tfMPTUnlock,
|
||||
({{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfHolder, SoeOptional}}))
|
||||
/** This permission grants the delegated account the ability to unlock MPToken. */
|
||||
PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548)
|
||||
|
||||
@@ -222,63 +222,8 @@ public:
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function can be overridden to introduce additional semantic constraints beyond the
|
||||
* granular template validation for granular permissions. It is called by the base
|
||||
* invokeCheckPermission method only after the transaction has successfully passed
|
||||
* checkGranularSandbox.
|
||||
*/
|
||||
static NotTEC
|
||||
checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the transaction is authorized to be executed by the delegated account.
|
||||
* This function enforces the strict permission check hierarchy. It is explicitly
|
||||
* designed NOT to be overridden. Derived transactors must instead implement
|
||||
* checkGranularSemantics to add custom validation logic for granular permissions.
|
||||
*
|
||||
* The evaluation proceeds as follows:
|
||||
* - If transaction-level permission is granted, the function immediately returns tesSUCCESS.
|
||||
* - If transaction-level permission is not granted, the function checks whether the transaction
|
||||
* matches the granular permission template defined in permissions.macro. If it does, it then
|
||||
* calls checkGranularSemantics to perform any additional, fine-grained validation.
|
||||
*
|
||||
*/
|
||||
template <class T>
|
||||
static NotTEC
|
||||
invokeCheckPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
// heldGranularPermissions is passed by reference into checkPermission.
|
||||
// It is populated with the sender’s granular permissions only when the sender
|
||||
// lacks tx-level permission but has granular permissions that satisfy the
|
||||
// granular permission template.
|
||||
//
|
||||
// - result is terNO_DELEGATE_PERMISSION: return immediately.
|
||||
// - result is tesSUCCESS and heldGranularPermissions is empty: tx-level permission was
|
||||
// granted, so we returned success before populating it.
|
||||
// - result is tesSUCCESS and heldGranularPermissions is not empty: tx-level permission was
|
||||
// not granted, but the held granular permissions passed checkGranularSandbox, so we proceed
|
||||
// to checkGranularSemantics.
|
||||
//
|
||||
// WARNING: Do not simplify checkPermission to return only
|
||||
// heldGranularPermissions or the ter code. Both the result and the
|
||||
// populated set are required to enforce the strict permission hierarchy
|
||||
// described above.
|
||||
std::unordered_set<GranularPermissionType> heldGranularPermissions;
|
||||
if (NotTEC const result = checkPermission(view, tx, heldGranularPermissions);
|
||||
!isTesSuccess(result) || heldGranularPermissions.empty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return T::checkGranularSemantics(view, tx, heldGranularPermissions);
|
||||
}
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
// Interface used by AccountDelete
|
||||
@@ -408,12 +353,6 @@ protected:
|
||||
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
|
||||
|
||||
private:
|
||||
static NotTEC
|
||||
checkPermission(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType>& heldGranularPermissions);
|
||||
|
||||
std::pair<TER, XRPAmount>
|
||||
reset(XRPAmount fee);
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -32,10 +32,7 @@ public:
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
@@ -22,6 +22,9 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -21,10 +21,7 @@ public:
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
@@ -17,10 +17,6 @@ ProtectHome=true
|
||||
PrivateTmp=true
|
||||
User=xrpld
|
||||
Group=xrpld
|
||||
StateDirectory=xrpld
|
||||
StateDirectoryMode=0750
|
||||
LogsDirectory=xrpld
|
||||
LogsDirectoryMode=0750
|
||||
LimitNOFILE=65536
|
||||
SystemCallArchitectures=native
|
||||
|
||||
|
||||
@@ -1,136 +1,91 @@
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/SOTemplate.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TxFlags.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
Permission::GranularPermissionEntry::GranularPermissionEntry(
|
||||
std::string name,
|
||||
TxType txType,
|
||||
std::uint32_t permittedFlags,
|
||||
std::vector<SOElement> permittedFields)
|
||||
: name(std::move(name))
|
||||
, txType(txType)
|
||||
, permittedFlags(permittedFlags)
|
||||
, permittedFields(std::move(permittedFields), TxFormats::getCommonFields())
|
||||
{
|
||||
}
|
||||
|
||||
Permission::Permission()
|
||||
{
|
||||
{
|
||||
txFeatureMap_ = {
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, delegable, amendment, ...) \
|
||||
txDelegationMap_[static_cast<TxType>(value)] = {amendment, delegable};
|
||||
#define TRANSACTION(tag, value, name, delegable, amendment, ...) {value, amendment},
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
#undef TRANSACTION
|
||||
#pragma pop_macro("TRANSACTION")
|
||||
}
|
||||
|
||||
granularPermissionsByName_ = {
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
|
||||
#define GRANULAR_PERMISSION(type, ...) {#type, type},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
};
|
||||
|
||||
{
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
delegableTx_ = {
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) \
|
||||
granularPermissions_.emplace( \
|
||||
std::piecewise_construct, \
|
||||
std::forward_as_tuple(GranularPermissionType::type), \
|
||||
std::forward_as_tuple( \
|
||||
#type, txType, static_cast<std::uint32_t>(flags), std::vector<SOElement> fields));
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
#define TRANSACTION(tag, value, name, delegable, ...) {value, delegable},
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
#undef TRANSACTION
|
||||
#pragma pop_macro("TRANSACTION")
|
||||
};
|
||||
|
||||
granularPermissionMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {#type, type},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
}
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
if (granularPermissionsByName_.size() != granularPermissions_.size())
|
||||
granularNameMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {type, #type},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
granularTxTypeMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {type, txType},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
XRPL_ASSERT(
|
||||
txFeatureMap_.size() == delegableTx_.size(),
|
||||
"xrpl::Permission : txFeatureMap_ and delegableTx_ must have same "
|
||||
"size");
|
||||
|
||||
for ([[maybe_unused]] auto const& permission : granularPermissionMap_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>(
|
||||
"granularPermissionsByName_ and granularPermissions_ must have same size");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
for (auto const& [name, type] : granularPermissionsByName_)
|
||||
{
|
||||
if (type <= UINT16_MAX)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>(
|
||||
"Granular permission value must exceed the maximum uint16_t value: " + name);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& [type, entry] : granularPermissions_)
|
||||
granularTxTypes_.insert(entry.txType);
|
||||
|
||||
// Validate that all fields listed in permissions.macro exist in the
|
||||
// corresponding transaction type's format, catching typos at startup.
|
||||
for (auto const& [type, entry] : granularPermissions_)
|
||||
{
|
||||
if (!txDelegationMap_.contains(entry.txType))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>("Invalid granular permission txType in txDelegationMap_");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const* fmt = TxFormats::getInstance().findByType(entry.txType);
|
||||
if (fmt == nullptr)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>("Invalid granular permission txType");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
for (auto const& field : entry.permittedFields)
|
||||
{
|
||||
if (fmt->getSOTemplate().getIndex(field.sField()) == -1)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>("Invalid granular permission field");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
XRPL_ASSERT(
|
||||
permission.second > UINT16_MAX,
|
||||
"xrpl::Permission::granularPermissionMap_ : granular permission "
|
||||
"value must not exceed the maximum uint16_t value.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +97,8 @@ Permission::getInstance()
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
Permission::getPermissionName(std::uint32_t value) const
|
||||
Permission::getPermissionName(std::uint32_t const value) const
|
||||
{
|
||||
if (value == 0)
|
||||
return std::nullopt;
|
||||
|
||||
auto const permissionValue = static_cast<GranularPermissionType>(value);
|
||||
if (auto const granular = getGranularName(permissionValue))
|
||||
return granular;
|
||||
@@ -162,131 +114,90 @@ Permission::getPermissionName(std::uint32_t value) const
|
||||
std::optional<std::uint32_t>
|
||||
Permission::getGranularValue(std::string const& name) const
|
||||
{
|
||||
auto const it = granularPermissionsByName_.find(name);
|
||||
if (it != granularPermissionsByName_.end())
|
||||
auto const it = granularPermissionMap_.find(name);
|
||||
if (it != granularPermissionMap_.end())
|
||||
return static_cast<uint32_t>(it->second);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
Permission::getGranularName(GranularPermissionType value) const
|
||||
Permission::getGranularName(GranularPermissionType const& value) const
|
||||
{
|
||||
auto const it = granularPermissions_.find(value);
|
||||
if (it != granularPermissions_.end())
|
||||
return it->second.name;
|
||||
auto const it = granularNameMap_.find(value);
|
||||
if (it != granularNameMap_.end())
|
||||
return it->second;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TxType>
|
||||
Permission::getGranularTxType(GranularPermissionType gpType) const
|
||||
Permission::getGranularTxType(GranularPermissionType const& gpType) const
|
||||
{
|
||||
auto const it = granularPermissions_.find(gpType);
|
||||
if (it != granularPermissions_.end())
|
||||
return it->second.txType;
|
||||
auto const it = granularTxTypeMap_.find(gpType);
|
||||
if (it != granularTxTypeMap_.end())
|
||||
return it->second;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool
|
||||
Permission::hasGranularPermissions(TxType txType) const
|
||||
{
|
||||
return granularTxTypes_.contains(txType);
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<uint256 const>>
|
||||
Permission::getTxFeature(TxType txType) const
|
||||
{
|
||||
auto const it = txDelegationMap_.find(txType);
|
||||
auto const txFeaturesIt = txFeatureMap_.find(txType);
|
||||
XRPL_ASSERT(
|
||||
it != txDelegationMap_.end(),
|
||||
"xrpl::Permission::getTxFeature : tx exists in txDelegationMap_");
|
||||
txFeaturesIt != txFeatureMap_.end(),
|
||||
"xrpl::Permissions::getTxFeature : tx exists in txFeatureMap_");
|
||||
|
||||
if (it->second.amendment == uint256{})
|
||||
if (txFeaturesIt->second == uint256{})
|
||||
return std::nullopt;
|
||||
|
||||
return std::optional{std::cref(it->second.amendment)};
|
||||
return txFeaturesIt->second;
|
||||
}
|
||||
|
||||
bool
|
||||
Permission::isDelegable(std::uint32_t permissionValue, Rules const& rules) const
|
||||
Permission::isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const
|
||||
{
|
||||
if (permissionValue == 0)
|
||||
return false; // LCOV_EXCL_LINE
|
||||
|
||||
auto const amendmentEnabled = [&rules](TxDelegationEntry const& entry) {
|
||||
return entry.amendment == uint256{} || rules.enabled(entry.amendment);
|
||||
};
|
||||
|
||||
// Granular permissions may authorize a limited subset of a tx type even
|
||||
// when the full tx type is not delegable. They still require the
|
||||
// underlying transaction amendment to be enabled.
|
||||
if (auto const granularIt =
|
||||
granularPermissions_.find(static_cast<GranularPermissionType>(permissionValue));
|
||||
granularIt != granularPermissions_.end())
|
||||
auto const granularPermission =
|
||||
getGranularName(static_cast<GranularPermissionType>(permissionValue));
|
||||
if (granularPermission)
|
||||
{
|
||||
auto const txIt = txDelegationMap_.find(granularIt->second.txType);
|
||||
return txIt != txDelegationMap_.end() && amendmentEnabled(txIt->second);
|
||||
// granular permissions are always allowed to be delegated
|
||||
return true;
|
||||
}
|
||||
|
||||
auto const txType = permissionToTxType(permissionValue);
|
||||
auto const txIt = txDelegationMap_.find(txType);
|
||||
auto const it = delegableTx_.find(txType);
|
||||
|
||||
// Tx-level permissions require the transaction type itself to be delegable, and
|
||||
// the corresponding amendment enabled.
|
||||
return txIt != txDelegationMap_.end() && txIt->second.delegable != NotDelegable &&
|
||||
amendmentEnabled(txIt->second);
|
||||
if (it == delegableTx_.end())
|
||||
return false;
|
||||
|
||||
auto const txFeaturesIt = txFeatureMap_.find(txType);
|
||||
XRPL_ASSERT(
|
||||
txFeaturesIt != txFeatureMap_.end(),
|
||||
"xrpl::Permissions::isDelegable : tx exists in txFeatureMap_");
|
||||
|
||||
// Delegation is only allowed if the required amendment for the transaction
|
||||
// is enabled. For transactions that do not require an amendment, delegation
|
||||
// is always allowed.
|
||||
if (txFeaturesIt->second != uint256{} && !rules.enabled(txFeaturesIt->second))
|
||||
return false;
|
||||
|
||||
if (it->second == Delegation::NotDelegable)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Permission::txToPermissionType(TxType const type)
|
||||
Permission::txToPermissionType(TxType const& type)
|
||||
{
|
||||
return static_cast<uint32_t>(type) + 1;
|
||||
}
|
||||
|
||||
TxType
|
||||
Permission::permissionToTxType(uint32_t value)
|
||||
Permission::permissionToTxType(uint32_t const& value)
|
||||
{
|
||||
XRPL_ASSERT(value > 0, "xrpl::Permission::permissionToTxType : value is greater than 0");
|
||||
return static_cast<TxType>(value - 1);
|
||||
}
|
||||
|
||||
bool
|
||||
Permission::checkGranularSandbox(
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldPermissions) const
|
||||
{
|
||||
// Build union of flags upfront to enable an early exit. Fields are not stored and
|
||||
// grouped in advance to avoid heap allocation.
|
||||
std::uint32_t unionFlags = 0;
|
||||
for (auto const& gp : heldPermissions)
|
||||
{
|
||||
auto const it = granularPermissions_.find(gp);
|
||||
if (it != granularPermissions_.end())
|
||||
unionFlags |= it->second.permittedFlags;
|
||||
}
|
||||
|
||||
// Check if flags are permitted
|
||||
if ((tx.getFlags() & ~unionFlags) != 0)
|
||||
return false;
|
||||
|
||||
// Check if fields are permitted. Every present field must appear in at least one held
|
||||
// permission's template. The common fields are included in the constructor.
|
||||
for (auto const& field : tx)
|
||||
{
|
||||
if (field.getSType() == STI_NOTPRESENT)
|
||||
continue;
|
||||
|
||||
if (!std::ranges::any_of(heldPermissions, [&](auto const& gp) {
|
||||
auto const it = granularPermissions_.find(gp);
|
||||
return it != granularPermissions_.end() &&
|
||||
it->second.permittedFields.getIndex(field.getFName()) != -1;
|
||||
}))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -217,7 +217,7 @@ STTx::getFeePayer() const
|
||||
{
|
||||
// If sfDelegate is present, the delegate account is the payer
|
||||
// note: if a delegate is specified, its authorization to act on behalf of the account is
|
||||
// enforced in `Transactor::invokeCheckPermission`
|
||||
// enforced in `Transactor::checkPermission`
|
||||
// cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`)
|
||||
if (isFieldPresent(sfDelegate))
|
||||
return getAccountID(sfDelegate);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
@@ -47,7 +46,6 @@
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -177,16 +175,6 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
|
||||
if (ctx.tx[sfDelegate] == ctx.tx[sfAccount])
|
||||
return temBAD_SIGNER;
|
||||
|
||||
auto const& perm = Permission::getInstance();
|
||||
auto const txType = ctx.tx.getTxnType();
|
||||
|
||||
// If the transaction is not delegable and does not have granular permissions, fail earlier
|
||||
// with temINVALID. This is to prevent transactions that are not delegable at all from
|
||||
// being processed further in the invokeCheckPermission function.
|
||||
if (!perm.isDelegable(Permission::txToPermissionType(txType), ctx.rules) &&
|
||||
!perm.hasGranularPermissions(txType))
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
if (auto const ret = preflight0(ctx, flagMask))
|
||||
@@ -307,33 +295,19 @@ Transactor::preflightSigValidated(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
NotTEC
|
||||
Transactor::checkPermission(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType>& heldGranularPermissions)
|
||||
Transactor::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const sle = view.read(keylet::delegate(tx[sfAccount], *delegate));
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!Permission::getInstance().hasGranularPermissions(tx.getTxnType()))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
heldGranularPermissions = getGranularPermission(sle, tx.getTxnType());
|
||||
if (heldGranularPermissions.empty())
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (!Permission::getInstance().checkGranularSandbox(tx, heldGranularPermissions))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
return checkTxPermission(sle, tx);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
|
||||
@@ -181,8 +181,7 @@ invokePreclaim(PreclaimContext const& ctx)
|
||||
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result =
|
||||
Transactor::invokeCheckPermission<T>(ctx.view, ctx.tx))
|
||||
if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result = T::checkSign(ctx))
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -19,11 +20,13 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -165,6 +168,54 @@ AccountSet::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
AccountSet::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
// AccountSet is prohibited to be granted on a transaction level,
|
||||
// but some granular permissions are allowed.
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttACCOUNT_SET, granularPermissions);
|
||||
|
||||
auto const uSetFlag = tx.getFieldU32(sfSetFlag);
|
||||
auto const uClearFlag = tx.getFieldU32(sfClearFlag);
|
||||
// We don't support any flag based granular permission under
|
||||
// AccountSet transaction. If any delegated account is trying to
|
||||
// update the flag on behalf of another account, it is not
|
||||
// authorized.
|
||||
if (uSetFlag != 0 || uClearFlag != 0 || ((tx.getFlags() & tfUniversalMask) != 0u))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfEmailHash) && !granularPermissions.contains(AccountEmailHashSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfWalletLocator) || tx.isFieldPresent(sfNFTokenMinter))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfMessageKey) && !granularPermissions.contains(AccountMessageKeySet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfDomain) && !granularPermissions.contains(AccountDomainSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfTransferRate) && !granularPermissions.contains(AccountTransferRateSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfTickSize) && !granularPermissions.contains(AccountTickSizeSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -29,12 +29,14 @@ checkTxPermission(SLE::const_ref delegate, STTx const& tx)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
}
|
||||
|
||||
std::unordered_set<GranularPermissionType>
|
||||
getGranularPermission(SLE::const_ref delegate, TxType const& txType)
|
||||
void
|
||||
loadGranularPermission(
|
||||
SLE::const_ref delegate,
|
||||
TxType const& txType,
|
||||
std::unordered_set<GranularPermissionType>& granularPermissions)
|
||||
{
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
if (!delegate)
|
||||
return granularPermissions;
|
||||
return;
|
||||
|
||||
auto const permissionArray = delegate->getFieldArray(sfPermissions);
|
||||
for (auto const& permission : permissionArray)
|
||||
@@ -45,8 +47,6 @@ getGranularPermission(SLE::const_ref delegate, TxType const& txType)
|
||||
if (type && *type == txType)
|
||||
granularPermissions.insert(granularValue);
|
||||
}
|
||||
|
||||
return granularPermissions;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
@@ -28,6 +29,7 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -271,24 +273,38 @@ Payment::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
NotTEC
|
||||
Payment::checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
|
||||
Payment::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttPAYMENT, granularPermissions);
|
||||
|
||||
auto const& dstAmount = tx.getFieldAmount(sfAmount);
|
||||
auto const& amountAsset = dstAmount.asset();
|
||||
|
||||
// Granular permissions are only valid for direct payments.
|
||||
if (tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
|
||||
if ((tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset) ||
|
||||
tx.isFieldPresent(sfPaths))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
// PaymentMint and PaymentBurn apply to both IOU and MPT direct payments.
|
||||
if (heldGranularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
|
||||
if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
|
||||
amountAsset.getIssuer() == tx[sfAccount])
|
||||
return tesSUCCESS;
|
||||
|
||||
if (heldGranularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) &&
|
||||
if (granularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) &&
|
||||
amountAsset.getIssuer() == tx[sfDestination])
|
||||
return tesSUCCESS;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -15,12 +16,14 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -135,6 +138,39 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
MPTokenIssuanceSet::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
// this is added in case more flags will be added for MPTokenIssuanceSet
|
||||
// in the future. Currently unreachable.
|
||||
if ((tx.getFlags() & tfMPTokenIssuanceSetMask) != 0u)
|
||||
return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttMPTOKEN_ISSUANCE_SET, granularPermissions);
|
||||
|
||||
if (tx.isFlag(tfMPTLock) && !granularPermissions.contains(MPTokenIssuanceLock))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFlag(tfMPTUnlock) && !granularPermissions.contains(MPTokenIssuanceUnlock))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -20,6 +21,7 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
@@ -122,21 +124,51 @@ TrustSet::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
NotTEC
|
||||
TrustSet::checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
|
||||
TrustSet::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
// Currently we only support TrustlineAuthorize, TrustlineFreeze and
|
||||
// TrustlineUnfreeze granular permission. Setting other flags returns
|
||||
// error.
|
||||
if ((tx.getFlags() & tfTrustSetPermissionMask) != 0u)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfQualityIn) || tx.isFieldPresent(sfQualityOut))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
auto const saLimitAmount = tx.getFieldAmount(sfLimitAmount);
|
||||
auto const sleRippleState = view.read(
|
||||
keylet::line(
|
||||
tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.get<Issue>().currency));
|
||||
|
||||
// granular permissions are not allowed to create a trustline
|
||||
// if the trustline does not exist, granular permissions are
|
||||
// not allowed to create trustline
|
||||
if (!sleRippleState)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
// updating LimitAmount is not allowed with granular permissions,
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttTRUST_SET, granularPermissions);
|
||||
|
||||
if (tx.isFlag(tfSetfAuth) && !granularPermissions.contains(TrustlineAuthorize))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
if (tx.isFlag(tfSetFreeze) && !granularPermissions.contains(TrustlineFreeze))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
if (tx.isFlag(tfClearFreeze) && !granularPermissions.contains(TrustlineUnfreeze))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
// updating LimitAmount is not allowed only with granular permissions,
|
||||
// unless there's a new granular permission for this in the future.
|
||||
auto const curLimit = tx[sfAccount] > saLimitAmount.getIssuer()
|
||||
? sleRippleState->getFieldAmount(sfHighLimit)
|
||||
|
||||
@@ -5,11 +5,8 @@
|
||||
#include <test/jtx/acctdelete.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/balance.h>
|
||||
#include <test/jtx/batch.h>
|
||||
#include <test/jtx/delegate.h>
|
||||
#include <test/jtx/delivermin.h>
|
||||
#include <test/jtx/did.h>
|
||||
#include <test/jtx/domain.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
@@ -25,9 +22,7 @@
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
@@ -38,7 +33,6 @@
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -47,7 +41,6 @@
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -1070,93 +1063,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentMint/PaymentBurn with sfSendMax of the same asset is allowed,
|
||||
// same-asset SendMax is still a direct payment, not cross-currency.
|
||||
{
|
||||
Env env(*this, features);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
auto const usd = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.trust(usd(200), alice);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
// sfSendMax with same asset as sfAmount, still a direct payment
|
||||
env(pay(gw, alice, usd(50)), Sendmax(usd(50)), delegate::As(bob));
|
||||
env.require(Balance(alice, usd(50)));
|
||||
|
||||
env(delegate::set(alice, bob, {"PaymentBurn"}));
|
||||
env.close();
|
||||
|
||||
env(pay(alice, gw, usd(30)), Sendmax(usd(30)), delegate::As(bob));
|
||||
env.require(Balance(alice, usd(20)));
|
||||
}
|
||||
|
||||
// Test invalid fields or flags not allowed in granular permission template
|
||||
{
|
||||
Env env(*this, features);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
auto const usd = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.trust(usd(200), alice);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env(delegate::set(alice, bob, {"PaymentBurn"}));
|
||||
env.close();
|
||||
|
||||
// sfDeliverMin (with tfPartialPayment) is not in the PaymentMint
|
||||
// or PaymentBurn template.
|
||||
env(pay(gw, alice, usd(100)),
|
||||
DeliverMin(usd(50)),
|
||||
Txflags(tfPartialPayment),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
env(pay(alice, gw, usd(50)),
|
||||
DeliverMin(usd(25)),
|
||||
Txflags(tfPartialPayment),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// sfDomainID is not in the PaymentMint or PaymentBurn template.
|
||||
env(pay(gw, alice, usd(100)),
|
||||
Domain(uint256{1}),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
env(pay(alice, gw, usd(50)),
|
||||
Domain(uint256{1}),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// Delegate account holds no granular permissions for the tx type:
|
||||
// getGranularPermission returns empty set.
|
||||
{
|
||||
Env env(*this, features);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
auto const usd = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.trust(usd(200), alice);
|
||||
env.close();
|
||||
|
||||
// Bob holds only an AccountSet granular permission.
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
// Payment has granular permissions defined in permissions.macro,
|
||||
// but bob only holds AccountSet's granular permission,
|
||||
// getGranularPermission returns empty.
|
||||
env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// PaymentMint and PaymentBurn for MPT
|
||||
{
|
||||
std::string logs;
|
||||
@@ -1213,40 +1119,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(env.balance(bob, MPT) == bobMPT + MPT(100));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify granular permissions of different tx types in the same SLE are scoped
|
||||
// correctly. AccountSet permissions don't apply to Payment and vice versa
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
auto const usd = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.trust(usd(200), alice);
|
||||
env.close();
|
||||
|
||||
// Alice granted bob with both AccountDomainSet and PaymentMint.
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet", "PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
// PaymentMint fails at granular semantic check because alice is not the issuer.
|
||||
env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// AccountDomainSet applies correctly to AccountSet
|
||||
std::string const domain = "example.com";
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain] = strHex(domain);
|
||||
jt[sfDelegate] = bob.human();
|
||||
env(jt);
|
||||
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
|
||||
|
||||
// gw gives bob PaymentMint and bob can mint on gw's behalf
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
env(pay(gw, alice, usd(50)), delegate::As(bob));
|
||||
env.require(Balance(alice, usd(50)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1429,34 +1301,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfFullyCanonicalSig),
|
||||
delegate::As(bob));
|
||||
}
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(alice, gw["USD"](50)));
|
||||
env.close();
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
|
||||
env.close();
|
||||
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
|
||||
env.close();
|
||||
|
||||
// sfQualityOut is a valid TrustSet field, but not permitted in granular template
|
||||
json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
|
||||
txJson[sfQualityOut.jsonName] = 100;
|
||||
env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// tfSetNoRipple is a valid flag for TrustSet, but not permitted in granular template
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfSetNoRipple),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1612,9 +1456,7 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
env(jv2, Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// can not set AccountSet flags on behalf of other account,
|
||||
// in permissions.macro, the template for AccountSet does
|
||||
// not allow any flag set or clear.
|
||||
// can not set AccountSet flags on behalf of other account
|
||||
{
|
||||
Env env(*this);
|
||||
auto const alice = Account{"alice"};
|
||||
@@ -1710,71 +1552,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
env(jt);
|
||||
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
|
||||
}
|
||||
|
||||
// setting invalid field not in permissions.macro template will be rejected.
|
||||
{
|
||||
Env env(*this);
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Alice gives Bob permission to set her Domain
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
std::string const domain = "example.com";
|
||||
auto txJson = noop(alice);
|
||||
txJson[sfDomain] = strHex(domain);
|
||||
txJson[sfDelegate] = bob.human();
|
||||
|
||||
// sfNFTokenMinter is a valid field in AccountSet tx, but
|
||||
// it is not permitted for granular template
|
||||
txJson[sfNFTokenMinter] = bob.human();
|
||||
|
||||
env(txJson, Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// Delegated AccountSet with no fields and no flags is allowed,
|
||||
// because it is allowed in the non-delegated case as well.
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
auto jt = noop(alice);
|
||||
jt[sfDelegate] = bob.human();
|
||||
env(jt);
|
||||
}
|
||||
|
||||
// Revoking all permissions deletes the SLE and subsequent attempts are rejected.
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
std::string const domain = "example.com";
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain] = strHex(domain);
|
||||
jt[sfDelegate] = bob.human();
|
||||
env(jt);
|
||||
|
||||
// empty DelegateSet deletes the SLE
|
||||
env(delegate::set(alice, bob, {}));
|
||||
env.close();
|
||||
|
||||
env(jt, Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1895,37 +1672,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
env.close();
|
||||
mpt.set({.account = alice, .flags = tfMPTLock | tfFullyCanonicalSig, .delegate = bob});
|
||||
}
|
||||
|
||||
// field not permitted to exist in granular delegation
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
|
||||
MPTTester mpt(env, alice, {.fund = false});
|
||||
mpt.create({.flags = tfMPTCanLock});
|
||||
env.close();
|
||||
|
||||
// alice gives granular permission to bob for MPTokenIssuanceLock
|
||||
env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
|
||||
env.close();
|
||||
|
||||
// Field is not permitted, permitted fields for delegation is defined in
|
||||
// permissions.macro.
|
||||
mpt.set(
|
||||
{.account = alice,
|
||||
.mutableFlags = 2,
|
||||
.delegate = bob,
|
||||
.err = terNO_DELEGATE_PERMISSION});
|
||||
|
||||
// Notice: flags not defined in permissions.macro are not permitted for delegation.
|
||||
// Since preflight will check invalid flag for the tx, it is not reachable.
|
||||
// If any new flag is defined into the transaction in the future,
|
||||
// but is not allowed for delegation, the transaction will be rejected with
|
||||
// terNO_DELEGATE_PERMISSION. The set of permitted flags for delegation is defined in
|
||||
// permissions.macro.
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2395,62 +2141,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
for (auto const& tx : txRequiredFeatures)
|
||||
txAmendmentEnabled(tx.first);
|
||||
}
|
||||
|
||||
// Granular permissions also require the amendment for their underlying
|
||||
// transaction type.
|
||||
{
|
||||
for (auto const permission : {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"})
|
||||
{
|
||||
Env env(*this, features - featureMPTokensV1);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {permission}), Ter(temMALFORMED));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGranularSandboxCheckOrder()
|
||||
{
|
||||
testcase("Make sure GranularSandbox is checked after transaction-level permission");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(alice, gw["USD"](50)));
|
||||
env.close();
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
|
||||
env.close();
|
||||
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
|
||||
env.close();
|
||||
|
||||
// sfQualityOut is a valid TrustSet field, but not permitted in granular template
|
||||
json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
|
||||
txJson[sfQualityOut.jsonName] = 100;
|
||||
env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// Now Alice grants Bob with transaction level permission
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize", "TrustSet"}));
|
||||
env.close();
|
||||
|
||||
// NOTE: This case is to ensure that if a delegate possesses a
|
||||
// transaction-level permission (e.g., TrustSet), the granular sandbox must not incorrectly
|
||||
// block the transaction. The function checkGranularSandbox MUST be called after the
|
||||
// transaction-level permission check. This test case is to avoid future refactor mistakes,
|
||||
// modifying the order will fail here.
|
||||
env(txJson, delegate::As(bob));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2503,94 +2193,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
"\n Action: Verify security requirements to interact with Delegation feature");
|
||||
}
|
||||
|
||||
void
|
||||
testNonDelegableTxWithDelegate(FeatureBitset features)
|
||||
{
|
||||
testcase("non-delegable tx with sfDelegate is rejected at preflight");
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Transactions that are notDelegable and have no granular permissions
|
||||
// will be rejected with temINVALID at preflight.
|
||||
// Note: pseudo-transactions (EnableAmendment, SetFee and UNLModify) are also
|
||||
// notDelegable but are excluded here — passesLocalChecks() blocks them
|
||||
// before preflight1 is ever reached.
|
||||
{
|
||||
// SetRegularKey, SignerListSet, AccountDelete, DelegateSet.
|
||||
env(regkey(alice, bob), delegate::As(bob), Ter(temINVALID));
|
||||
env(signers(alice, 1, {{bob, 1}}), delegate::As(bob), Ter(temINVALID));
|
||||
env(acctdelete(alice, bob), delegate::As(bob), Ter(temINVALID));
|
||||
env(delegate::set(alice, bob, {"Payment"}), delegate::As(bob), Ter(temINVALID));
|
||||
|
||||
// SAV transactions.
|
||||
{
|
||||
Vault const vault{env};
|
||||
auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()});
|
||||
env(createTx, delegate::As(bob), Ter(temINVALID));
|
||||
|
||||
env(vault.set({.owner = alice, .id = keylet.key}),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
env(vault.del({.owner = alice, .id = keylet.key}),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(1)}),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(1)}),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
env(vault.clawback({.issuer = alice, .id = keylet.key, .holder = bob}),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
}
|
||||
|
||||
// Batch transaction: the outer Batch itself is non-delegable.
|
||||
{
|
||||
auto const seq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 1);
|
||||
env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(pay(alice, bob, XRP(1)), seq + 1),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
}
|
||||
|
||||
// Lending protocol transactions
|
||||
{
|
||||
Vault const vault{env};
|
||||
auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()});
|
||||
env(createTx);
|
||||
|
||||
env(loanBroker::set(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
|
||||
env(loanBroker::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
|
||||
env(loanBroker::coverDeposit(alice, keylet.key, XRP(1)),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
env(loanBroker::coverWithdraw(alice, keylet.key, XRP(1)),
|
||||
delegate::As(bob),
|
||||
Ter(temINVALID));
|
||||
env(loanBroker::coverClawback(alice), delegate::As(bob), Ter(temINVALID));
|
||||
|
||||
env(loan::set(alice, keylet.key, Number(100)), delegate::As(bob), Ter(temINVALID));
|
||||
env(loan::manage(alice, keylet.key, 0), delegate::As(bob), Ter(temINVALID));
|
||||
env(loan::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
|
||||
env(loan::pay(alice, keylet.key, XRP(1)), delegate::As(bob), Ter(temINVALID));
|
||||
}
|
||||
}
|
||||
|
||||
// AccountSet is notDelegable at tx level but has granular permissions,
|
||||
// so sfDelegate passes preflight and is rejected at invokeCheckPermission with
|
||||
// terNO_DELEGATE_PERMISSION.
|
||||
{
|
||||
env(fset(alice, asfDefaultRipple), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDelegateUtilsNullptrCheck()
|
||||
{
|
||||
@@ -2600,8 +2202,9 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
STTx const tx{ttPAYMENT, [](STObject&) {}};
|
||||
BEAST_EXPECT(checkTxPermission(nullptr, tx) == terNO_DELEGATE_PERMISSION);
|
||||
|
||||
// getGranularPermission nullptr check
|
||||
auto const granularPermissions = getGranularPermission(nullptr, ttPAYMENT);
|
||||
// loadGranularPermission nullptr check
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(nullptr, ttPAYMENT, granularPermissions);
|
||||
BEAST_EXPECT(granularPermissions.empty());
|
||||
}
|
||||
|
||||
@@ -2631,9 +2234,7 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
testSignForDelegated();
|
||||
testPermissionValue(all);
|
||||
testTxRequireFeatures(all);
|
||||
testGranularSandboxCheckOrder();
|
||||
testTxDelegableCount();
|
||||
testNonDelegableTxWithDelegate(all);
|
||||
testDelegateUtilsNullptrCheck();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,17 +5,21 @@
|
||||
#include <test/jtx/noop.h>
|
||||
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/misc/SHAMapStore.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/ToString.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -111,6 +115,73 @@ class LedgerMaster_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCompleteLedgerRange(FeatureBitset features)
|
||||
{
|
||||
// Note that this test is intentionally very similar to
|
||||
// SHAMapStore_test::testLedgerGaps, but has a different
|
||||
// focus.
|
||||
|
||||
testcase("Complete Ledger operations");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
auto const deleteInterval = 8;
|
||||
|
||||
Env env{*this, envconfig([](auto cfg) {
|
||||
return onlineDelete(std::move(cfg), deleteInterval);
|
||||
})};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
LedgerIndex minSeq = 2;
|
||||
LedgerIndex maxSeq = env.closed()->header().seq;
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
LedgerIndex lastRotated = store.getLastRotated();
|
||||
BEAST_EXPECTS(maxSeq == 3, to_string(maxSeq));
|
||||
BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers());
|
||||
BEAST_EXPECTS(lastRotated == 3, to_string(lastRotated));
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
|
||||
|
||||
// Close enough ledgers to rotate a few times
|
||||
for (int i = 0; i < 24; ++i)
|
||||
{
|
||||
for (int t = 0; t < 3; ++t)
|
||||
{
|
||||
env(noop(alice));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
++maxSeq;
|
||||
|
||||
if (maxSeq == lastRotated + deleteInterval)
|
||||
{
|
||||
minSeq = lastRotated;
|
||||
lastRotated = maxSeq;
|
||||
}
|
||||
BEAST_EXPECTS(
|
||||
env.closed()->header().seq == maxSeq, to_string(env.closed()->header().seq));
|
||||
BEAST_EXPECTS(store.getLastRotated() == lastRotated, to_string(store.getLastRotated()));
|
||||
std::stringstream expectedRange;
|
||||
expectedRange << minSeq << "-" << maxSeq;
|
||||
BEAST_EXPECTS(lm.getCompleteLedgers() == expectedRange.str(), lm.getCompleteLedgers());
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -124,6 +195,7 @@ public:
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
testTxnIdFromIndex(features);
|
||||
testCompleteLedgerRange(features);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/jtx/noop.h>
|
||||
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/main/NodeStoreScheduler.h>
|
||||
#include <xrpld/app/misc/SHAMapStore.h>
|
||||
@@ -9,6 +11,7 @@
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/config/BasicConfig.h>
|
||||
@@ -25,12 +28,15 @@
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -42,10 +48,7 @@ class SHAMapStore_test : public beast::unit_test::Suite
|
||||
static auto
|
||||
onlineDelete(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg->ledgerHistory = kDeleteInterval;
|
||||
auto& section = cfg->section(Sections::kNodeDatabase);
|
||||
section.set(Keys::kOnlineDelete, std::to_string(kDeleteInterval));
|
||||
return cfg;
|
||||
return jtx::onlineDelete(std::move(cfg), kDeleteInterval);
|
||||
}
|
||||
|
||||
static auto
|
||||
@@ -143,11 +146,11 @@ class SHAMapStore_test : public beast::unit_test::Suite
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
|
||||
int ledgerSeq = 3;
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
BEAST_EXPECT(!store.getLastRotated());
|
||||
|
||||
env.close();
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
auto ledger = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++)));
|
||||
@@ -227,7 +230,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(kDeleteInterval + 4)));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + 3);
|
||||
lastRotated = store.getLastRotated();
|
||||
@@ -254,7 +257,7 @@ public:
|
||||
!getHash(ledgers[i]).empty());
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + lastRotated);
|
||||
|
||||
@@ -292,7 +295,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
// The database will always have back to ledger 2,
|
||||
// regardless of lastRotated.
|
||||
@@ -307,7 +310,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
BEAST_EXPECT(lastRotated != store.getLastRotated());
|
||||
@@ -323,7 +326,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, kDeleteInterval + 1, lastRotated);
|
||||
BEAST_EXPECT(lastRotated != store.getLastRotated());
|
||||
@@ -362,7 +365,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, ledgerSeq - 2, 2);
|
||||
BEAST_EXPECT(lastRotated == store.getLastRotated());
|
||||
@@ -372,7 +375,7 @@ public:
|
||||
BEAST_EXPECT(!RPC::containsError(canDelete[jss::result]));
|
||||
BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq + (kDeleteInterval / 2));
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, ledgerSeq - 2, 2);
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
@@ -385,7 +388,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
|
||||
@@ -401,7 +404,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
@@ -413,7 +416,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, ledgerSeq - firstBatch, firstBatch);
|
||||
|
||||
@@ -435,7 +438,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
@@ -447,7 +450,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
|
||||
@@ -468,7 +471,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
BEAST_EXPECT(store.getLastRotated() == lastRotated);
|
||||
|
||||
@@ -480,7 +483,7 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true));
|
||||
}
|
||||
|
||||
store.rendezvous();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
ledgerCheck(env, ledgerSeq - lastRotated, lastRotated);
|
||||
|
||||
@@ -603,6 +606,165 @@ public:
|
||||
BEAST_EXPECT(dbr->getName() == "3");
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerGaps()
|
||||
{
|
||||
// Note that this test is intentionally very similar to
|
||||
// LedgerMaster_test::testCompleteLedgerRange, but has a different
|
||||
// focus.
|
||||
|
||||
testcase("Wait for ledger gaps to fill in");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, envconfig(onlineDelete)};
|
||||
|
||||
auto failureMessage = [&](char const* label, auto expected, auto actual) {
|
||||
std::stringstream ss;
|
||||
ss << label << ": Expected: " << expected << ", Got: " << actual;
|
||||
return ss.str();
|
||||
};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
LedgerIndex minSeq = 2;
|
||||
LedgerIndex maxSeq = env.closed()->header().seq;
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
LedgerIndex lastRotated = store.getLastRotated();
|
||||
BEAST_EXPECTS(maxSeq == 3, std::to_string(maxSeq));
|
||||
BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers());
|
||||
BEAST_EXPECTS(lastRotated == 3, to_string(lastRotated));
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
|
||||
|
||||
// Close enough ledgers to rotate a few times
|
||||
while (maxSeq < 20)
|
||||
{
|
||||
for (int t = 0; t < 3; ++t)
|
||||
{
|
||||
env(noop(alice));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
++maxSeq;
|
||||
|
||||
if (maxSeq + 1 == lastRotated + kDeleteInterval)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// The next ledger will trigger a rotation. Delete the
|
||||
// current ledger from LedgerMaster.
|
||||
std::this_thread::sleep_for(100ms);
|
||||
LedgerIndex const deleteSeq = maxSeq;
|
||||
{
|
||||
std::size_t iterations = 30;
|
||||
while (!lm.haveLedger(deleteSeq) && --iterations > 0)
|
||||
{
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
BEAST_EXPECTS(iterations > 25, to_string(iterations));
|
||||
}
|
||||
lm.clearLedger(deleteSeq);
|
||||
|
||||
auto expectedRange = [](auto minSeq, auto deleteSeq, auto maxSeq) {
|
||||
std::stringstream expectedRange;
|
||||
expectedRange << minSeq << "-" << (deleteSeq - 1);
|
||||
if (deleteSeq + 1 == maxSeq)
|
||||
{
|
||||
expectedRange << "," << maxSeq;
|
||||
}
|
||||
else if (deleteSeq < maxSeq)
|
||||
{
|
||||
expectedRange << "," << (deleteSeq + 1) << "-" << maxSeq;
|
||||
}
|
||||
return expectedRange.str();
|
||||
};
|
||||
BEAST_EXPECTS(
|
||||
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
|
||||
failureMessage(
|
||||
"Complete ledgers",
|
||||
expectedRange(minSeq, deleteSeq, maxSeq),
|
||||
lm.getCompleteLedgers()));
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 1);
|
||||
|
||||
// Close another ledger, which will trigger a rotation, but the
|
||||
// rotation will be stuck until the missing ledger is filled in.
|
||||
env.close();
|
||||
// DO NOT CALL rendezvous()! You'll end up with a deadlock.
|
||||
++maxSeq;
|
||||
|
||||
// Nothing has changed
|
||||
BEAST_EXPECTS(
|
||||
store.getLastRotated() == lastRotated,
|
||||
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
|
||||
BEAST_EXPECTS(
|
||||
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
|
||||
failureMessage(
|
||||
"Complete ledgers",
|
||||
expectedRange(minSeq, deleteSeq, maxSeq),
|
||||
lm.getCompleteLedgers()));
|
||||
|
||||
// Close 5 more ledgers, waiting one second in between to
|
||||
// simulate the ledger making progress while online delete waits
|
||||
// for the missing ledger to be filled in.
|
||||
// This ensures the healthWait check has time to run and
|
||||
// detect the gap.
|
||||
for (int l = 0; l < 5; ++l)
|
||||
{
|
||||
env.close();
|
||||
++maxSeq;
|
||||
// Nothing has changed
|
||||
BEAST_EXPECTS(
|
||||
store.getLastRotated() == lastRotated,
|
||||
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
|
||||
BEAST_EXPECTS(
|
||||
lm.getCompleteLedgers() == expectedRange(minSeq, deleteSeq, maxSeq),
|
||||
failureMessage(
|
||||
"Complete Ledgers",
|
||||
expectedRange(minSeq, deleteSeq, maxSeq),
|
||||
lm.getCompleteLedgers()));
|
||||
// The Store is "stuck" in healthWait() and won't finish the run() loop until
|
||||
// it's backfilled
|
||||
BEAST_EXPECT(!store.rendezvous(100ms));
|
||||
}
|
||||
|
||||
// Put the missing ledger back in LedgerMaster
|
||||
lm.setLedgerRangePresent(deleteSeq, deleteSeq);
|
||||
|
||||
// Wait for the rotation to finish
|
||||
BEAST_EXPECT(store.rendezvous());
|
||||
|
||||
minSeq = lastRotated;
|
||||
lastRotated = deleteSeq + 1;
|
||||
}
|
||||
BEAST_EXPECT(maxSeq != lastRotated + kDeleteInterval);
|
||||
BEAST_EXPECTS(
|
||||
env.closed()->header().seq == maxSeq,
|
||||
failureMessage("maxSeq", maxSeq, env.closed()->header().seq));
|
||||
BEAST_EXPECTS(
|
||||
store.getLastRotated() == lastRotated,
|
||||
failureMessage("lastRotated", lastRotated, store.getLastRotated()));
|
||||
std::stringstream expectedRange;
|
||||
expectedRange << minSeq << "-" << maxSeq;
|
||||
BEAST_EXPECTS(
|
||||
lm.getCompleteLedgers() == expectedRange.str(),
|
||||
failureMessage("CompleteLedgers", expectedRange.str(), lm.getCompleteLedgers()));
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
|
||||
BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -610,6 +772,7 @@ public:
|
||||
testAutomatic();
|
||||
testCanDelete();
|
||||
testRotate();
|
||||
testLedgerGaps();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,6 +51,17 @@ envconfig(F&& modfunc, Args&&... args)
|
||||
return modfunc(envconfig(), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// @brief adjust config to enable online_delete
|
||||
///
|
||||
/// @param cfg config instance to be modified
|
||||
///
|
||||
/// @param deleteInterval how many new ledgers should be available before
|
||||
/// rotating. Defaults to 8, because the standalone minimum is 8.
|
||||
///
|
||||
/// @return unique_ptr to Config instance
|
||||
std::unique_ptr<Config>
|
||||
onlineDelete(std::unique_ptr<Config> cfg, std::uint32_t deleteInterval = 8);
|
||||
|
||||
/// @brief adjust config so no admin ports are enabled
|
||||
///
|
||||
/// this is intended for use with envconfig, as in
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
#include <xrpl/config/Constants.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -60,6 +62,15 @@ setupConfigForUnitTests(Config& cfg)
|
||||
|
||||
namespace jtx {
|
||||
|
||||
std::unique_ptr<Config>
|
||||
onlineDelete(std::unique_ptr<Config> cfg, std::uint32_t deleteInterval)
|
||||
{
|
||||
cfg->ledgerHistory = deleteInterval;
|
||||
auto& section = cfg->section(Sections::kNodeDatabase);
|
||||
section.set(Keys::kOnlineDelete, std::to_string(deleteInterval));
|
||||
return cfg;
|
||||
}
|
||||
|
||||
std::unique_ptr<Config>
|
||||
noAdmin(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
|
||||
@@ -105,7 +105,10 @@ public:
|
||||
failedSave(std::uint32_t seq, uint256 const& hash);
|
||||
|
||||
std::string
|
||||
getCompleteLedgers();
|
||||
getCompleteLedgers() const;
|
||||
|
||||
std::size_t
|
||||
missingFromCompleteLedgerRange(LedgerIndex first, LedgerIndex last) const;
|
||||
|
||||
/** Apply held transactions to the open ledger
|
||||
This is normally called as we close the ledger.
|
||||
@@ -322,7 +325,7 @@ private:
|
||||
// A set of transactions to replay during the next close
|
||||
std::unique_ptr<LedgerReplay> replayData_;
|
||||
|
||||
std::recursive_mutex completeLock_;
|
||||
std::recursive_mutex mutable completeLock_;
|
||||
RangeSet<std::uint32_t> completeLedgers_;
|
||||
|
||||
// Publish thread is running.
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <boost/icl/concept/interval_associator.hpp>
|
||||
#include <boost/icl/concept/interval_set.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
@@ -1568,12 +1569,25 @@ LedgerMaster::getPublishedLedger()
|
||||
}
|
||||
|
||||
std::string
|
||||
LedgerMaster::getCompleteLedgers()
|
||||
LedgerMaster::getCompleteLedgers() const
|
||||
{
|
||||
std::scoped_lock const sl(completeLock_);
|
||||
return to_string(completeLedgers_);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
LedgerMaster::missingFromCompleteLedgerRange(LedgerIndex first, LedgerIndex last) const
|
||||
{
|
||||
RangeSet<LedgerIndex> const target{range(first, last)};
|
||||
|
||||
auto const missing = [&target, this] {
|
||||
std::scoped_lock const sl(completeLock_);
|
||||
return target - completeLedgers_;
|
||||
}();
|
||||
|
||||
return boost::icl::size(missing);
|
||||
}
|
||||
|
||||
std::optional<NetClock::time_point>
|
||||
LedgerMaster::getCloseTimeBySeq(LedgerIndex ledgerIndex)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/ledger/Ledger.h>
|
||||
#include <xrpl/nodestore/Manager.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -27,8 +28,8 @@ public:
|
||||
virtual void
|
||||
start() = 0;
|
||||
|
||||
virtual void
|
||||
rendezvous() const = 0;
|
||||
[[nodiscard]] virtual bool
|
||||
rendezvous(std::optional<std::chrono::milliseconds> const& timeout = {}) const = 0;
|
||||
|
||||
virtual void
|
||||
stop() = 0;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
@@ -233,14 +234,21 @@ SHAMapStoreImp::onLedgerClosed(std::shared_ptr<Ledger const> const& ledger)
|
||||
cond_.notify_one();
|
||||
}
|
||||
|
||||
void
|
||||
SHAMapStoreImp::rendezvous() const
|
||||
bool
|
||||
SHAMapStoreImp::rendezvous(std::optional<std::chrono::milliseconds> const& timeout) const
|
||||
{
|
||||
if (!working_)
|
||||
return;
|
||||
return true;
|
||||
|
||||
auto notWorking = [&] { return !working_; };
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
rendezvous_.wait(lock, [&] { return !working_; });
|
||||
if (timeout)
|
||||
{
|
||||
return rendezvous_.wait_for(lock, *timeout, notWorking);
|
||||
}
|
||||
rendezvous_.wait(lock, notWorking);
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
@@ -311,6 +319,16 @@ SHAMapStoreImp::run()
|
||||
bool const readyToRotate = validatedSeq >= lastRotated + deleteInterval_ &&
|
||||
canDelete_ >= lastRotated - 1 && healthWait() == HealthResult::KeepGoing;
|
||||
|
||||
{
|
||||
JLOG(journal_.debug()) << "run: Setting lastGoodValidatedLedger_ to " << validatedSeq;
|
||||
// Note that this is set after the healthWait() check, so that we
|
||||
// don't start the rotation until the validated ledger is fully
|
||||
// processed. It is not guaranteed to be done at this point. It also
|
||||
// allows the testLedgerGaps unit test to work.
|
||||
std::unique_lock<std::mutex> const lock(mutex_);
|
||||
lastGoodValidatedLedger_ = validatedSeq;
|
||||
}
|
||||
|
||||
// will delete up to (not including) lastRotated
|
||||
if (readyToRotate)
|
||||
{
|
||||
@@ -318,7 +336,8 @@ SHAMapStoreImp::run()
|
||||
<< lastRotated << " deleteInterval " << deleteInterval_
|
||||
<< " canDelete_ " << canDelete_ << " state "
|
||||
<< app_.getOPs().strOperatingMode(false) << " age "
|
||||
<< ledgerMaster_->getValidatedLedgerAge().count() << 's';
|
||||
<< ledgerMaster_->getValidatedLedgerAge().count()
|
||||
<< "s. Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
|
||||
|
||||
clearPrior(lastRotated);
|
||||
if (healthWait() == HealthResult::Stopping)
|
||||
@@ -378,7 +397,9 @@ SHAMapStoreImp::run()
|
||||
clearCaches(validatedSeq);
|
||||
});
|
||||
|
||||
JLOG(journal_.warn()) << "finished rotation " << validatedSeq;
|
||||
JLOG(journal_.warn()) << "finished rotation. validatedSeq: " << validatedSeq
|
||||
<< ", lastRotated: " << lastRotated
|
||||
<< ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -622,20 +643,36 @@ SHAMapStoreImp::clearPrior(LedgerIndex lastRotated)
|
||||
SHAMapStoreImp::HealthResult
|
||||
SHAMapStoreImp::healthWait()
|
||||
{
|
||||
auto index = ledgerMaster_->getValidLedgerIndex();
|
||||
auto age = ledgerMaster_->getValidatedLedgerAge();
|
||||
OperatingMode mode = netOPs_->getOperatingMode();
|
||||
std::unique_lock lock(mutex_);
|
||||
while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_))
|
||||
|
||||
auto const waitTime = recoveryWaitTime_;
|
||||
auto const ageThreshold = ageThreshold_;
|
||||
auto numMissing = lastGoodValidatedLedger_ == 0
|
||||
? 0
|
||||
: ledgerMaster_->missingFromCompleteLedgerRange(lastGoodValidatedLedger_, index);
|
||||
while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold || numMissing > 0))
|
||||
{
|
||||
lock.unlock();
|
||||
JLOG(journal_.warn()) << "Waiting " << recoveryWaitTime_.count()
|
||||
<< "s for node to stabilize. state: "
|
||||
<< app_.getOPs().strOperatingMode(mode, false) << ". age "
|
||||
<< age.count() << 's';
|
||||
std::this_thread::sleep_for(recoveryWaitTime_);
|
||||
// this value shouldn't change, so grab it while we have the
|
||||
// lock
|
||||
auto const lowerBound = lastGoodValidatedLedger_;
|
||||
|
||||
ScopeUnlock const unlock(lock);
|
||||
|
||||
auto const stream =
|
||||
mode != OperatingMode::FULL || age > ageThreshold ? journal_.warn() : journal_.info();
|
||||
JLOG(stream) << "Waiting " << waitTime.count() << "s for node to stabilize. state: "
|
||||
<< app_.getOPs().strOperatingMode(mode, false) << ". age " << age.count()
|
||||
<< "s. Missing ledgers: " << numMissing << ". Expect: " << lowerBound << "-"
|
||||
<< index << ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
|
||||
std::this_thread::sleep_for(waitTime);
|
||||
index = ledgerMaster_->getValidLedgerIndex();
|
||||
age = ledgerMaster_->getValidatedLedgerAge();
|
||||
mode = netOPs_->getOperatingMode();
|
||||
lock.lock();
|
||||
numMissing =
|
||||
lowerBound == 0 ? 0 : ledgerMaster_->missingFromCompleteLedgerRange(lowerBound, index);
|
||||
}
|
||||
|
||||
return stop_ ? HealthResult::Stopping : HealthResult::KeepGoing;
|
||||
|
||||
@@ -72,6 +72,11 @@ private:
|
||||
std::thread thread_;
|
||||
bool stop_ = false;
|
||||
bool healthy_ = true;
|
||||
// Used to prevent ledger gaps from forming during online deletion. Keeps
|
||||
// track of the last validated ledger that was processed without gaps. There
|
||||
// are no guarantees about gaps while online delete is not running. For
|
||||
// that, use advisory_delete and check for gaps externally.
|
||||
LedgerIndex lastGoodValidatedLedger_ = 0;
|
||||
mutable std::condition_variable cond_;
|
||||
mutable std::condition_variable rendezvous_;
|
||||
mutable std::mutex mutex_;
|
||||
@@ -85,11 +90,11 @@ private:
|
||||
std::uint32_t deleteBatch_ = 100;
|
||||
std::chrono::milliseconds backOff_{100};
|
||||
std::chrono::seconds ageThreshold_{60};
|
||||
/// If the node is out of sync during an online_delete healthWait()
|
||||
/// call, sleep the thread for this time, and continue checking until
|
||||
/// recovery.
|
||||
/// If the node is out of sync, or any recent ledgers are not
|
||||
/// available during an online_delete healthWait() call, sleep
|
||||
/// the thread for this time, and continue checking until recovery.
|
||||
/// See also: "recovery_wait_seconds" in xrpld-example.cfg
|
||||
std::chrono::seconds recoveryWaitTime_{5};
|
||||
std::chrono::seconds recoveryWaitTime_{2};
|
||||
|
||||
// these do not exist upon SHAMapStore creation, but do exist
|
||||
// as of run() or before
|
||||
@@ -145,8 +150,8 @@ public:
|
||||
void
|
||||
onLedgerClosed(std::shared_ptr<Ledger const> const& ledger) override;
|
||||
|
||||
void
|
||||
rendezvous() const override;
|
||||
bool
|
||||
rendezvous(std::optional<std::chrono::milliseconds> const& timeout = {}) const override;
|
||||
int
|
||||
fdRequired() const override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user