#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 using feature_hashed_unique = boost::multi_index::hashed_unique< boost::multi_index::tag, boost::multi_index::member>; // Intermediate types to help with readability using feature_indexing = boost::multi_index::indexed_by< boost::multi_index::random_access< boost::multi_index::tag>, feature_hashed_unique, feature_hashed_unique>; // This multi_index_container provides access to the features collection by // name, index, and uint256 feature identifier boost::multi_index::multi_index_container features; std::map all; std::map supported; std::size_t upVotes = 0; std::size_t downVotes = 0; mutable std::atomic 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(); return sequence[i]; } size_t getIndex(Feature const& feature) const { auto const& sequence = features.get(); 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(); 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(); auto const name_it = name_index.find(name); return name_it == name_index.end() ? nullptr : &*name_it; } public: FeatureCollections(); std::optional 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 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 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 FeatureCollections::getRegisteredFeature(std::string const& name) const { XRPL_ASSERT( readOnly.load(), "xrpl::FeatureCollections::getRegisteredFeature : startup completed"); Feature const* feature = getByName(name); if (feature) 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) { 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; } else // 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) 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 ? feature->name : to_string(f); } static FeatureCollections featureCollections; } // namespace /** All amendments libxrpl knows of. */ std::map 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 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 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 #define XRPL_FEATURE(name, supported, vote) \ uint256 const feature##name = registerFeature(#name, supported, vote); #define XRPL_FIX(name, supported, vote) \ uint256 const fix##name = registerFeature("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 #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