mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-04 19:12:29 +00:00
448 lines
14 KiB
C++
448 lines
14 KiB
C++
#include <xrpl/basics/Slice.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/basics/contract.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/digest.h>
|
|
|
|
#include <boost/container_hash/hash.hpp>
|
|
#include <boost/multi_index/hashed_index.hpp>
|
|
#include <boost/multi_index/indexed_by.hpp>
|
|
#include <boost/multi_index/member.hpp>
|
|
#include <boost/multi_index/random_access_index.hpp>
|
|
#include <boost/multi_index/tag.hpp>
|
|
#include <boost/multi_index_container.hpp>
|
|
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
namespace xrpl {
|
|
|
|
inline std::size_t
|
|
hash_value(xrpl::uint256 const& feature)
|
|
{
|
|
std::size_t seed = 0;
|
|
using namespace boost;
|
|
for (auto const& n : feature)
|
|
hash_combine(seed, n);
|
|
return seed;
|
|
}
|
|
|
|
namespace {
|
|
|
|
enum class Supported : bool { no = false, yes };
|
|
|
|
// *NOTE*
|
|
//
|
|
// Features, or Amendments as they are called elsewhere, are enabled on the
|
|
// network at some specific time based on Validator voting. Features are
|
|
// enabled using run-time conditionals based on the state of the amendment.
|
|
// There is value in retaining that conditional code for some time after
|
|
// the amendment is enabled to make it simple to replay old transactions.
|
|
// However, once an amendment has been enabled for, say, more than two years
|
|
// then retaining that conditional code has less value since it is
|
|
// uncommon to replay such old transactions.
|
|
//
|
|
// Starting in January of 2020 Amendment conditionals from before January
|
|
// 2018 are being removed. So replaying any ledger from before January
|
|
// 2018 needs to happen on an older version of the server code. There's
|
|
// a log message in Application.cpp that warns about replaying old ledgers.
|
|
//
|
|
// At some point in the future someone may wish to remove amendment
|
|
// conditional code for amendments that were enabled after January 2018.
|
|
// When that happens then the log message in Application.cpp should be
|
|
// updated.
|
|
//
|
|
// Generally, amendments which introduce new features should be set as
|
|
// "VoteBehavior::DefaultNo" whereas in rare cases, amendments that fix
|
|
// critical bugs should be set as "VoteBehavior::DefaultYes", if off-chain
|
|
// consensus is reached amongst reviewers, validator operators, and other
|
|
// participants.
|
|
|
|
class FeatureCollections
|
|
{
|
|
struct Feature
|
|
{
|
|
std::string name;
|
|
uint256 feature;
|
|
|
|
Feature() = delete;
|
|
explicit Feature(std::string const& name_, uint256 const& feature_)
|
|
: name(name_), feature(feature_)
|
|
{
|
|
}
|
|
|
|
// These structs are used by the `features` multi_index_container to
|
|
// provide access to the features collection by size_t index, string
|
|
// name, and uint256 feature identifier
|
|
struct byIndex
|
|
{
|
|
};
|
|
struct byName
|
|
{
|
|
};
|
|
struct byFeature
|
|
{
|
|
};
|
|
};
|
|
|
|
// Intermediate types to help with readability
|
|
template <class tag, typename Type, Type Feature::* PtrToMember>
|
|
using feature_hashed_unique = boost::multi_index::hashed_unique<
|
|
boost::multi_index::tag<tag>,
|
|
boost::multi_index::member<Feature, Type, PtrToMember>>;
|
|
|
|
// Intermediate types to help with readability
|
|
using feature_indexing = boost::multi_index::indexed_by<
|
|
boost::multi_index::random_access<boost::multi_index::tag<Feature::byIndex>>,
|
|
feature_hashed_unique<Feature::byFeature, uint256, &Feature::feature>,
|
|
feature_hashed_unique<Feature::byName, std::string, &Feature::name>>;
|
|
|
|
// This multi_index_container provides access to the features collection by
|
|
// name, index, and uint256 feature identifier
|
|
boost::multi_index::multi_index_container<Feature, feature_indexing> features;
|
|
std::map<std::string, AmendmentSupport> all;
|
|
std::map<std::string, VoteBehavior> supported;
|
|
std::size_t upVotes = 0;
|
|
std::size_t downVotes = 0;
|
|
mutable std::atomic<bool> readOnly = false;
|
|
|
|
// These helper functions provide access to the features collection by name,
|
|
// index, and uint256 feature identifier, so the details of
|
|
// multi_index_container can be hidden
|
|
Feature const&
|
|
getByIndex(size_t i) const
|
|
{
|
|
if (i >= features.size())
|
|
LogicError("Invalid FeatureBitset index");
|
|
auto const& sequence = features.get<Feature::byIndex>();
|
|
return sequence[i];
|
|
}
|
|
size_t
|
|
getIndex(Feature const& feature) const
|
|
{
|
|
auto const& sequence = features.get<Feature::byIndex>();
|
|
auto const it_to = sequence.iterator_to(feature);
|
|
return it_to - sequence.begin();
|
|
}
|
|
Feature const*
|
|
getByFeature(uint256 const& feature) const
|
|
{
|
|
auto const& feature_index = features.get<Feature::byFeature>();
|
|
auto const feature_it = feature_index.find(feature);
|
|
return feature_it == feature_index.end() ? nullptr : &*feature_it;
|
|
}
|
|
Feature const*
|
|
getByName(std::string const& name) const
|
|
{
|
|
auto const& name_index = features.get<Feature::byName>();
|
|
auto const name_it = name_index.find(name);
|
|
return name_it == name_index.end() ? nullptr : &*name_it;
|
|
}
|
|
|
|
public:
|
|
FeatureCollections();
|
|
|
|
std::optional<uint256>
|
|
getRegisteredFeature(std::string const& name) const;
|
|
|
|
uint256
|
|
registerFeature(std::string const& name, Supported support, VoteBehavior vote);
|
|
|
|
/** Tell FeatureCollections when registration is complete. */
|
|
bool
|
|
registrationIsDone();
|
|
|
|
std::size_t
|
|
featureToBitsetIndex(uint256 const& f) const;
|
|
|
|
uint256 const&
|
|
bitsetIndexToFeature(size_t i) const;
|
|
|
|
std::string
|
|
featureToName(uint256 const& f) const;
|
|
|
|
/** All amendments that are registered within the table. */
|
|
std::map<std::string, AmendmentSupport> const&
|
|
allAmendments() const
|
|
{
|
|
return all;
|
|
}
|
|
|
|
/** Amendments that this server supports.
|
|
Whether they are enabled depends on the Rules defined in the validated
|
|
ledger */
|
|
std::map<std::string, VoteBehavior> const&
|
|
supportedAmendments() const
|
|
{
|
|
return supported;
|
|
}
|
|
|
|
/** Amendments that this server WON'T vote for by default. */
|
|
std::size_t
|
|
numDownVotedAmendments() const
|
|
{
|
|
return downVotes;
|
|
}
|
|
|
|
/** Amendments that this server WILL vote for by default. */
|
|
std::size_t
|
|
numUpVotedAmendments() const
|
|
{
|
|
return upVotes;
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
FeatureCollections::FeatureCollections()
|
|
{
|
|
features.reserve(xrpl::detail::numFeatures);
|
|
}
|
|
|
|
std::optional<uint256>
|
|
FeatureCollections::getRegisteredFeature(std::string const& name) const
|
|
{
|
|
XRPL_ASSERT(
|
|
readOnly.load(), "xrpl::FeatureCollections::getRegisteredFeature : startup completed");
|
|
Feature const* feature = getByName(name);
|
|
if (feature != nullptr)
|
|
return feature->feature;
|
|
return std::nullopt;
|
|
}
|
|
|
|
void
|
|
check(bool condition, char const* logicErrorMessage)
|
|
{
|
|
if (!condition)
|
|
LogicError(logicErrorMessage);
|
|
}
|
|
|
|
uint256
|
|
FeatureCollections::registerFeature(std::string const& name, Supported support, VoteBehavior vote)
|
|
{
|
|
check(!readOnly, "Attempting to register a feature after startup.");
|
|
check(
|
|
support == Supported::yes || vote == VoteBehavior::DefaultNo,
|
|
"Invalid feature parameters. Must be supported to be up-voted.");
|
|
Feature const* i = getByName(name);
|
|
if (i == nullptr)
|
|
{
|
|
check(features.size() < detail::numFeatures, "More features defined than allocated.");
|
|
|
|
auto const f = sha512Half(Slice(name.data(), name.size()));
|
|
|
|
features.emplace_back(name, f);
|
|
|
|
auto const getAmendmentSupport = [=]() {
|
|
if (vote == VoteBehavior::Obsolete)
|
|
return AmendmentSupport::Retired;
|
|
return support == Supported::yes ? AmendmentSupport::Supported
|
|
: AmendmentSupport::Unsupported;
|
|
};
|
|
all.emplace(name, getAmendmentSupport());
|
|
|
|
if (support == Supported::yes)
|
|
{
|
|
supported.emplace(name, vote);
|
|
|
|
if (vote == VoteBehavior::DefaultYes)
|
|
{
|
|
++upVotes;
|
|
}
|
|
else
|
|
{
|
|
++downVotes;
|
|
}
|
|
}
|
|
check(upVotes + downVotes == supported.size(), "Feature counting logic broke");
|
|
check(supported.size() <= features.size(), "More supported features than defined features");
|
|
check(features.size() == all.size(), "The 'all' features list is populated incorrectly");
|
|
return f;
|
|
}
|
|
|
|
// Each feature should only be registered once
|
|
LogicError("Duplicate feature registration");
|
|
}
|
|
|
|
/** Tell FeatureCollections when registration is complete. */
|
|
bool
|
|
FeatureCollections::registrationIsDone()
|
|
{
|
|
readOnly = true;
|
|
return true;
|
|
}
|
|
|
|
size_t
|
|
FeatureCollections::featureToBitsetIndex(uint256 const& f) const
|
|
{
|
|
XRPL_ASSERT(
|
|
readOnly.load(), "xrpl::FeatureCollections::featureToBitsetIndex : startup completed");
|
|
|
|
Feature const* feature = getByFeature(f);
|
|
if (feature == nullptr)
|
|
LogicError("Invalid Feature ID");
|
|
|
|
return getIndex(*feature);
|
|
}
|
|
|
|
uint256 const&
|
|
FeatureCollections::bitsetIndexToFeature(size_t i) const
|
|
{
|
|
XRPL_ASSERT(
|
|
readOnly.load(), "xrpl::FeatureCollections::bitsetIndexToFeature : startup completed");
|
|
Feature const& feature = getByIndex(i);
|
|
return feature.feature;
|
|
}
|
|
|
|
std::string
|
|
FeatureCollections::featureToName(uint256 const& f) const
|
|
{
|
|
XRPL_ASSERT(readOnly.load(), "xrpl::FeatureCollections::featureToName : startup completed");
|
|
Feature const* feature = getByFeature(f);
|
|
return (feature != nullptr) ? feature->name : to_string(f);
|
|
}
|
|
|
|
FeatureCollections featureCollections;
|
|
|
|
} // namespace
|
|
|
|
/** All amendments libxrpl knows of. */
|
|
std::map<std::string, AmendmentSupport> const&
|
|
allAmendments()
|
|
{
|
|
return featureCollections.allAmendments();
|
|
}
|
|
|
|
/** Amendments that this server supports.
|
|
Whether they are enabled depends on the Rules defined in the validated
|
|
ledger */
|
|
std::map<std::string, VoteBehavior> const&
|
|
detail::supportedAmendments()
|
|
{
|
|
return featureCollections.supportedAmendments();
|
|
}
|
|
|
|
/** Amendments that this server won't vote for by default. */
|
|
std::size_t
|
|
detail::numDownVotedAmendments()
|
|
{
|
|
return featureCollections.numDownVotedAmendments();
|
|
}
|
|
|
|
/** Amendments that this server will vote for by default. */
|
|
std::size_t
|
|
detail::numUpVotedAmendments()
|
|
{
|
|
return featureCollections.numUpVotedAmendments();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
std::optional<uint256>
|
|
getRegisteredFeature(std::string const& name)
|
|
{
|
|
return featureCollections.getRegisteredFeature(name);
|
|
}
|
|
|
|
uint256
|
|
registerFeature(std::string const& name, Supported support, VoteBehavior vote)
|
|
{
|
|
return featureCollections.registerFeature(name, support, vote);
|
|
}
|
|
|
|
// Retired features are in the ledger and have no code controlled by the
|
|
// feature. They need to be supported, but do not need to be voted on.
|
|
uint256
|
|
retireFeature(std::string const& name)
|
|
{
|
|
return registerFeature(name, Supported::yes, VoteBehavior::Obsolete);
|
|
}
|
|
|
|
/** Tell FeatureCollections when registration is complete. */
|
|
bool
|
|
registrationIsDone()
|
|
{
|
|
return featureCollections.registrationIsDone();
|
|
}
|
|
|
|
size_t
|
|
featureToBitsetIndex(uint256 const& f)
|
|
{
|
|
return featureCollections.featureToBitsetIndex(f);
|
|
}
|
|
|
|
uint256
|
|
bitsetIndexToFeature(size_t i)
|
|
{
|
|
return featureCollections.bitsetIndexToFeature(i);
|
|
}
|
|
|
|
std::string
|
|
featureToName(uint256 const& f)
|
|
{
|
|
return featureCollections.featureToName(f);
|
|
}
|
|
|
|
// All known amendments must be registered either here or below with the
|
|
// "retired" amendments
|
|
|
|
#pragma push_macro("XRPL_FEATURE")
|
|
#undef XRPL_FEATURE
|
|
#pragma push_macro("XRPL_FIX")
|
|
#undef XRPL_FIX
|
|
#pragma push_macro("XRPL_RETIRE_FEATURE")
|
|
#undef XRPL_RETIRE_FEATURE
|
|
#pragma push_macro("XRPL_RETIRE_FIX")
|
|
#undef XRPL_RETIRE_FIX
|
|
|
|
consteval auto
|
|
enforceValidFeatureName(auto fn) -> char const*
|
|
{
|
|
static_assert(validFeatureName(fn), "Invalid feature name");
|
|
static_assert(validFeatureNameSize(fn), "Invalid feature name size");
|
|
return fn();
|
|
}
|
|
|
|
#define XRPL_FEATURE(name, supported, vote) \
|
|
uint256 const feature##name = \
|
|
registerFeature(enforceValidFeatureName([] { return #name; }), supported, vote);
|
|
#define XRPL_FIX(name, supported, vote) \
|
|
uint256 const fix##name = \
|
|
registerFeature(enforceValidFeatureName([] { return "fix" #name; }), supported, vote);
|
|
|
|
// clang-format off
|
|
#define XRPL_RETIRE_FEATURE(name) \
|
|
[[deprecated("The referenced feature amendment has been retired")]] \
|
|
[[maybe_unused]] \
|
|
uint256 const retiredFeature##name = retireFeature(#name);
|
|
|
|
#define XRPL_RETIRE_FIX(name) \
|
|
[[deprecated("The referenced fix amendment has been retired")]] \
|
|
[[maybe_unused]] \
|
|
uint256 const retiredFix##name = retireFeature("fix" #name);
|
|
// clang-format on
|
|
|
|
#include <xrpl/protocol/detail/features.macro>
|
|
|
|
#undef XRPL_RETIRE_FEATURE
|
|
#pragma pop_macro("XRPL_RETIRE_FEATURE")
|
|
#undef XRPL_RETIRE_FIX
|
|
#pragma pop_macro("XRPL_RETIRE_FIX")
|
|
#undef XRPL_FIX
|
|
#pragma pop_macro("XRPL_FIX")
|
|
#undef XRPL_FEATURE
|
|
#pragma pop_macro("XRPL_FEATURE")
|
|
|
|
// All of the features should now be registered, since variables in a cpp file
|
|
// are initialized from top to bottom.
|
|
//
|
|
// Use initialization of one final static variable to set
|
|
// featureCollections::readOnly.
|
|
[[maybe_unused]] static bool const readOnlySet = featureCollections.registrationIsDone();
|
|
|
|
} // namespace xrpl
|