diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 6df235bc2..83ca2538b 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -80,7 +80,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 108; +static constexpr std::size_t numFeatures = 109; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index c74ce54ff..dc37066c6 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -307,7 +307,7 @@ Keylet uritoken(AccountID const& issuer, Blob const& uri); Keylet -cron(uint32_t timestamp, AccountID const& id); +cron(uint32_t timestamp, std::optional const& id = std::nullopt); /** AMM entry */ Keylet diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 1bb82ed93..d2c87da4d 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -50,6 +50,7 @@ XRPL_FEATURE(DID, Supported::no, VoteBehavior::DefaultNo XRPL_FIX (DisallowIncomingV1, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes) diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index 8244a0bbe..e24b5b3e3 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -514,7 +514,7 @@ uritoken(AccountID const& issuer, Blob const& uri) // Examples: 100M → ~5.4e-12, 1B → ~5.4e-11, 10B → ~5.4e-10, 100B → ~5.4e-9 // (negligible). Keylet -cron(uint32_t timestamp, AccountID const& id) +cron(uint32_t timestamp, std::optional const& id) { static const uint256 ns = indexHash(LedgerNameSpace::CRON); @@ -529,7 +529,14 @@ cron(uint32_t timestamp, AccountID const& id) h[10] = static_cast((timestamp >> 8) & 0xFFU); h[11] = static_cast((timestamp >> 0) & 0xFFU); - const uint256 accHash = indexHash(LedgerNameSpace::CRON, timestamp, id); + if (!id.has_value()) + { + // final 20 bytes are zero + std::memset(h + 12, 0, 20); + return {ltCRON, uint256::fromVoid(h)}; + } + + const uint256 accHash = indexHash(LedgerNameSpace::CRON, timestamp, *id); // final 20 bytes are account ID std::memcpy(h + 12, accHash.cdata(), 20); diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index aed131945..cde9d6998 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -1502,9 +1502,13 @@ TxQ::accept(Application& app, OpenView& view) { uint32_t currentTime = view.parentCloseTime().time_since_epoch().count(); - uint256 klStart = keylet::cron(0, AccountID(beast::zero)).key; - uint256 const klEnd = - keylet::cron(currentTime + 1, AccountID(beast::zero)).key; + bool fixCron = view.rules().enabled(fixCronStacking); + std::optional accountID = std::nullopt; + if (!fixCron) + accountID = AccountID(beast::zero); + + uint256 klStart = keylet::cron(0, accountID).key; + uint256 const klEnd = keylet::cron(currentTime + 1, accountID).key; std::set cronAccs; diff --git a/src/xrpld/app/tx/detail/Cron.cpp b/src/xrpld/app/tx/detail/Cron.cpp index bfb5e9beb..a5eb4a8e9 100644 --- a/src/xrpld/app/tx/detail/Cron.cpp +++ b/src/xrpld/app/tx/detail/Cron.cpp @@ -93,6 +93,16 @@ Cron::doApply() auto& view = ctx_.view(); auto const& tx = ctx_.tx; + if (view.rules().enabled(fixCronStacking)) + { + if (auto const seq = tx.getFieldU32(sfLedgerSequence); + seq != view.info().seq) + { + JLOG(j_.warn()) << "Cron: wrong ledger seq=" << seq; + return tefFAILURE; + } + } + AccountID const& id = tx.getAccountID(sfOwner); auto sle = view.peek(keylet::account(id));