Files
rippled/src/libxrpl/protocol/Rules.cpp
Ed Hennis 8306ac7710 fix: Improve Number addition/subtraction rounding (#7369)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-11 00:18:31 +00:00

218 lines
6.2 KiB
C++

#include <xrpl/protocol/Rules.h>
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STVector256.h>
#include <memory>
#include <optional>
#include <unordered_set>
#include <utility>
namespace xrpl {
namespace {
// Use a static inside a function to help prevent order-of-initialization issues
LocalValue<std::optional<Rules>>&
getCurrentTransactionRulesRef()
{
static LocalValue<std::optional<Rules>> kR;
return kR;
}
} // namespace
std::optional<Rules> const&
getCurrentTransactionRules()
{
return *getCurrentTransactionRulesRef();
}
void
setCurrentTransactionRules(std::optional<Rules> r)
{
// Make global changes associated with the rules before the value is moved.
// Push the appropriate setting, instead of having the class pull every time
// the value is needed. That could get expensive fast.
// Declare the range this way to keep clang-tidy from complaining
auto const range = [&r]() {
// If any new conditions with new amendments are added to "enableLargeNumbers", those
// amendments must also be added to useRulesGuards.
bool const enableLargeNumbers =
!r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
// If enableLargeNumbers is true, then useRulesGuard must also return true.
// However, the reverse is not true. Other amendments can cause the rules guard to be used,
// even though large numbers are _not_ used.
XRPL_ASSERT(
!r || !enableLargeNumbers || useRulesGuards(*r),
"setCurrentTransactionRules : rule decisions match");
if (enableLargeNumbers)
{
static_assert(
MantissaRange::MantissaScale::Large == MantissaRange::MantissaScale::Large330);
if (!r || r->enabled(fixCleanup3_3_0))
{
return MantissaRange::MantissaScale::Large330;
}
if (r->enabled(fixCleanup3_2_0))
{
return MantissaRange::MantissaScale::Large320;
}
return MantissaRange::MantissaScale::LargeLegacy;
}
return MantissaRange::MantissaScale::Small;
}();
Number::setMantissaScale(range);
*getCurrentTransactionRulesRef() = std::move(r);
}
bool
useRulesGuards(Rules const& rules)
{
// The list of amendments used here - to decide whether to create a RulesGuard - must be a
// superset of the list used to determine "enableLargeNumbers" in setCurrentTransactionRules.
// Additional amendments can be added if desired.
//
// As soon as any one of these amendments is retired, this whole function can be removed, along
// with createGuards, and any other callers, and the first set of guards can be created directly
// at the call site, without using optional.
return rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol) ||
rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0);
}
void
createGuards(
Rules const& rules,
std::optional<CurrentTransactionRulesGuard>& rulesGuard,
std::optional<NumberMantissaScaleGuard>& mantissaScaleGuard)
{
if (useRulesGuards(rules))
{
// raii classes for the current ledger rules. If the rules are set, the MantissaRange will
// be updated, too.
rulesGuard.emplace(rules);
}
else
{
// Without those features enabled, always use the old number rules.
mantissaScaleGuard.emplace(MantissaRange::MantissaScale::Small);
}
}
class Rules::Impl
{
private:
std::unordered_set<uint256, HardenedHash<>> set_;
std::optional<uint256> digest_;
std::unordered_set<uint256, beast::Uhash<>> const& presets_;
public:
explicit Impl(std::unordered_set<uint256, beast::Uhash<>> const& presets) : presets_(presets)
{
}
Impl(
std::unordered_set<uint256, beast::Uhash<>> const& presets,
std::optional<uint256> const& digest,
STVector256 const& amendments)
: digest_(digest), presets_(presets)
{
set_.reserve(amendments.size());
set_.insert(amendments.begin(), amendments.end());
}
[[nodiscard]] std::unordered_set<uint256, beast::Uhash<>> const&
presets() const
{
return presets_;
}
[[nodiscard]] bool
enabled(uint256 const& feature) const
{
if (presets_.contains(feature))
return true;
return set_.contains(feature);
}
bool
operator==(Impl const& other) const
{
if (!digest_ && !other.digest_)
return true;
if (!digest_ || !other.digest_)
return false;
XRPL_ASSERT(
presets_ == other.presets_,
"xrpl::Rules::Impl::operator==(Impl) const : input presets do "
"match");
return *digest_ == *other.digest_;
}
};
Rules::Rules(std::unordered_set<uint256, beast::Uhash<>> const& presets)
: impl_(std::make_shared<Impl>(presets))
{
}
Rules::Rules(
std::unordered_set<uint256, beast::Uhash<>> const& presets,
std::optional<uint256> const& digest,
STVector256 const& amendments)
: impl_(std::make_shared<Impl>(presets, digest, amendments))
{
}
std::unordered_set<uint256, beast::Uhash<>> const&
Rules::presets() const
{
return impl_->presets();
}
bool
Rules::enabled(uint256 const& feature) const
{
XRPL_ASSERT(impl_, "xrpl::Rules::enabled : initialized");
return impl_->enabled(feature);
}
bool
Rules::operator==(Rules const& other) const
{
XRPL_ASSERT(impl_ && other.impl_, "xrpl::Rules::operator==(Rules) const : both initialized");
if (impl_.get() == other.impl_.get())
return true;
return *impl_ == *other.impl_;
}
bool
Rules::operator!=(Rules const& other) const
{
return !(*this == other);
}
bool
isFeatureEnabled(uint256 const& feature, bool resultIfNoRules)
{
auto const& rules = getCurrentTransactionRules();
if (!rules)
return resultIfNoRules;
return rules->enabled(feature);
}
bool
isFeatureEnabled(uint256 const& feature)
{
return isFeatureEnabled(feature, false);
}
} // namespace xrpl