rippled
Loading...
Searching...
No Matches
libxrpl/protocol/Feature.cpp
1#include <xrpl/basics/Slice.h>
2#include <xrpl/basics/base_uint.h>
3#include <xrpl/basics/contract.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/protocol/Feature.h>
6#include <xrpl/protocol/digest.h>
7
8#include <boost/container_hash/hash.hpp>
9#include <boost/multi_index/hashed_index.hpp>
10#include <boost/multi_index/indexed_by.hpp>
11#include <boost/multi_index/member.hpp>
12#include <boost/multi_index/random_access_index.hpp>
13#include <boost/multi_index/tag.hpp>
14#include <boost/multi_index_container.hpp>
15
16#include <atomic>
17#include <cstddef>
18#include <map>
19#include <optional>
20#include <string>
21
22namespace xrpl {
23
24inline std::size_t
26{
27 std::size_t seed = 0;
28 using namespace boost;
29 for (auto const& n : feature)
30 hash_combine(seed, n);
31 return seed;
32}
33
34namespace {
35
36enum class Supported : bool { no = false, yes };
37
38// *NOTE*
39//
40// Features, or Amendments as they are called elsewhere, are enabled on the
41// network at some specific time based on Validator voting. Features are
42// enabled using run-time conditionals based on the state of the amendment.
43// There is value in retaining that conditional code for some time after
44// the amendment is enabled to make it simple to replay old transactions.
45// However, once an amendment has been enabled for, say, more than two years
46// then retaining that conditional code has less value since it is
47// uncommon to replay such old transactions.
48//
49// Starting in January of 2020 Amendment conditionals from before January
50// 2018 are being removed. So replaying any ledger from before January
51// 2018 needs to happen on an older version of the server code. There's
52// a log message in Application.cpp that warns about replaying old ledgers.
53//
54// At some point in the future someone may wish to remove amendment
55// conditional code for amendments that were enabled after January 2018.
56// When that happens then the log message in Application.cpp should be
57// updated.
58//
59// Generally, amendments which introduce new features should be set as
60// "VoteBehavior::DefaultNo" whereas in rare cases, amendments that fix
61// critical bugs should be set as "VoteBehavior::DefaultYes", if off-chain
62// consensus is reached amongst reviewers, validator operators, and other
63// participants.
64
65class FeatureCollections
66{
67 struct Feature
68 {
69 std::string name;
70 uint256 feature;
71
72 Feature() = delete;
73 explicit Feature(std::string const& name_, uint256 const& feature_)
74 : name(name_), feature(feature_)
75 {
76 }
77
78 // These structs are used by the `features` multi_index_container to
79 // provide access to the features collection by size_t index, string
80 // name, and uint256 feature identifier
81 struct byIndex
82 {
83 };
84 struct byName
85 {
86 };
87 struct byFeature
88 {
89 };
90 };
91
92 // Intermediate types to help with readability
93 template <class tag, typename Type, Type Feature::* PtrToMember>
94 using feature_hashed_unique = boost::multi_index::hashed_unique<
95 boost::multi_index::tag<tag>,
96 boost::multi_index::member<Feature, Type, PtrToMember>>;
97
98 // Intermediate types to help with readability
99 using feature_indexing = boost::multi_index::indexed_by<
100 boost::multi_index::random_access<boost::multi_index::tag<Feature::byIndex>>,
101 feature_hashed_unique<Feature::byFeature, uint256, &Feature::feature>,
102 feature_hashed_unique<Feature::byName, std::string, &Feature::name>>;
103
104 // This multi_index_container provides access to the features collection by
105 // name, index, and uint256 feature identifier
106 boost::multi_index::multi_index_container<Feature, feature_indexing> features;
109 std::size_t upVotes = 0;
110 std::size_t downVotes = 0;
111 mutable std::atomic<bool> readOnly = false;
112
113 // These helper functions provide access to the features collection by name,
114 // index, and uint256 feature identifier, so the details of
115 // multi_index_container can be hidden
116 Feature const&
117 getByIndex(size_t i) const
118 {
119 if (i >= features.size())
120 LogicError("Invalid FeatureBitset index");
121 auto const& sequence = features.get<Feature::byIndex>();
122 return sequence[i];
123 }
124 size_t
125 getIndex(Feature const& feature) const
126 {
127 auto const& sequence = features.get<Feature::byIndex>();
128 auto const it_to = sequence.iterator_to(feature);
129 return it_to - sequence.begin();
130 }
131 Feature const*
132 getByFeature(uint256 const& feature) const
133 {
134 auto const& feature_index = features.get<Feature::byFeature>();
135 auto const feature_it = feature_index.find(feature);
136 return feature_it == feature_index.end() ? nullptr : &*feature_it;
137 }
138 Feature const*
139 getByName(std::string const& name) const
140 {
141 auto const& name_index = features.get<Feature::byName>();
142 auto const name_it = name_index.find(name);
143 return name_it == name_index.end() ? nullptr : &*name_it;
144 }
145
146public:
147 FeatureCollections();
148
150 getRegisteredFeature(std::string const& name) const;
151
152 uint256
153 registerFeature(std::string const& name, Supported support, VoteBehavior vote);
154
156 bool
158
160 featureToBitsetIndex(uint256 const& f) const;
161
162 uint256 const&
163 bitsetIndexToFeature(size_t i) const;
164
166 featureToName(uint256 const& f) const;
167
170 allAmendments() const
171 {
172 return all;
173 }
174
179 supportedAmendments() const
180 {
181 return supported;
182 }
183
187 {
188 return downVotes;
189 }
190
194 {
195 return upVotes;
196 }
197};
198
199//------------------------------------------------------------------------------
200
201FeatureCollections::FeatureCollections()
202{
203 features.reserve(xrpl::detail::numFeatures);
204}
205
207FeatureCollections::getRegisteredFeature(std::string const& name) const
208{
209 XRPL_ASSERT(
210 readOnly.load(), "xrpl::FeatureCollections::getRegisteredFeature : startup completed");
211 Feature const* feature = getByName(name);
212 if (feature != nullptr)
213 return feature->feature;
214 return std::nullopt;
215}
216
217void
218check(bool condition, char const* logicErrorMessage)
219{
220 if (!condition)
221 LogicError(logicErrorMessage);
222}
223
225FeatureCollections::registerFeature(std::string const& name, Supported support, VoteBehavior vote)
226{
227 check(!readOnly, "Attempting to register a feature after startup.");
228 check(
229 support == Supported::yes || vote == VoteBehavior::DefaultNo,
230 "Invalid feature parameters. Must be supported to be up-voted.");
231 Feature const* i = getByName(name);
232 if (i == nullptr)
233 {
234 check(features.size() < detail::numFeatures, "More features defined than allocated.");
235
236 auto const f = sha512Half(Slice(name.data(), name.size()));
237
238 features.emplace_back(name, f);
239
240 auto const getAmendmentSupport = [=]() {
241 if (vote == VoteBehavior::Obsolete)
243 return support == Supported::yes ? AmendmentSupport::Supported
245 };
246 all.emplace(name, getAmendmentSupport());
247
248 if (support == Supported::yes)
249 {
250 supported.emplace(name, vote);
251
252 if (vote == VoteBehavior::DefaultYes)
253 {
254 ++upVotes;
255 }
256 else
257 {
258 ++downVotes;
259 }
260 }
261 check(upVotes + downVotes == supported.size(), "Feature counting logic broke");
262 check(supported.size() <= features.size(), "More supported features than defined features");
263 check(features.size() == all.size(), "The 'all' features list is populated incorrectly");
264 return f;
265 }
266
267 // Each feature should only be registered once
268 LogicError("Duplicate feature registration");
269}
270
272bool
273FeatureCollections::registrationIsDone()
274{
275 readOnly = true;
276 return true;
277}
278
279size_t
280FeatureCollections::featureToBitsetIndex(uint256 const& f) const
281{
282 XRPL_ASSERT(
283 readOnly.load(), "xrpl::FeatureCollections::featureToBitsetIndex : startup completed");
284
285 Feature const* feature = getByFeature(f);
286 if (feature == nullptr)
287 LogicError("Invalid Feature ID");
288
289 return getIndex(*feature);
290}
291
292uint256 const&
293FeatureCollections::bitsetIndexToFeature(size_t i) const
294{
295 XRPL_ASSERT(
296 readOnly.load(), "xrpl::FeatureCollections::bitsetIndexToFeature : startup completed");
297 Feature const& feature = getByIndex(i);
298 return feature.feature;
299}
300
302FeatureCollections::featureToName(uint256 const& f) const
303{
304 XRPL_ASSERT(readOnly.load(), "xrpl::FeatureCollections::featureToName : startup completed");
305 Feature const* feature = getByFeature(f);
306 return (feature != nullptr) ? feature->name : to_string(f);
307}
308
309FeatureCollections featureCollections;
310
311} // namespace
312
316{
317 return featureCollections.allAmendments();
318}
319
325{
326 return featureCollections.supportedAmendments();
327}
328
332{
333 return featureCollections.numDownVotedAmendments();
334}
335
339{
340 return featureCollections.numUpVotedAmendments();
341}
342
343//------------------------------------------------------------------------------
344
347{
348 return featureCollections.getRegisteredFeature(name);
349}
350
353{
354 return featureCollections.registerFeature(name, support, vote);
355}
356
357// Retired features are in the ledger and have no code controlled by the
358// feature. They need to be supported, but do not need to be voted on.
361{
362 return registerFeature(name, Supported::yes, VoteBehavior::Obsolete);
363}
364
366bool
368{
369 return featureCollections.registrationIsDone();
370}
371
372size_t
374{
375 return featureCollections.featureToBitsetIndex(f);
376}
377
380{
381 return featureCollections.bitsetIndexToFeature(i);
382}
383
386{
387 return featureCollections.featureToName(f);
388}
389
390// All known amendments must be registered either here or below with the
391// "retired" amendments
392
393#pragma push_macro("XRPL_FEATURE")
394#undef XRPL_FEATURE
395#pragma push_macro("XRPL_FIX")
396#undef XRPL_FIX
397#pragma push_macro("XRPL_RETIRE_FEATURE")
398#undef XRPL_RETIRE_FEATURE
399#pragma push_macro("XRPL_RETIRE_FIX")
400#undef XRPL_RETIRE_FIX
401
402consteval auto
403enforceValidFeatureName(auto fn) -> char const*
404{
405 static_assert(validFeatureName(fn), "Invalid feature name");
406 static_assert(validFeatureNameSize(fn), "Invalid feature name size");
407 return fn();
408}
409
410#define XRPL_FEATURE(name, supported, vote) \
411 uint256 const feature##name = \
412 registerFeature(enforceValidFeatureName([] { return #name; }), supported, vote);
413#define XRPL_FIX(name, supported, vote) \
414 uint256 const fix##name = \
415 registerFeature(enforceValidFeatureName([] { return "fix" #name; }), supported, vote);
416
417// clang-format off
418#define XRPL_RETIRE_FEATURE(name) \
419 [[deprecated("The referenced feature amendment has been retired")]] \
420 [[maybe_unused]] \
421 uint256 const retiredFeature##name = retireFeature(#name);
422
423#define XRPL_RETIRE_FIX(name) \
424 [[deprecated("The referenced fix amendment has been retired")]] \
425 [[maybe_unused]] \
426 uint256 const retiredFix##name = retireFeature("fix" #name);
427// clang-format on
428
429#include <xrpl/protocol/detail/features.macro>
430
431#undef XRPL_RETIRE_FEATURE
432#pragma pop_macro("XRPL_RETIRE_FEATURE")
433#undef XRPL_RETIRE_FIX
434#pragma pop_macro("XRPL_RETIRE_FIX")
435#undef XRPL_FIX
436#pragma pop_macro("XRPL_FIX")
437#undef XRPL_FEATURE
438#pragma pop_macro("XRPL_FEATURE")
439
440// All of the features should now be registered, since variables in a cpp file
441// are initialized from top to bottom.
442//
443// Use initialization of one final static variable to set
444// featureCollections::readOnly.
445[[maybe_unused]] static bool const readOnlySet = featureCollections.registrationIsDone();
446
447} // namespace xrpl
T data(T... args)
T is_same_v
T load(T... args)
void check(bool condition, std::string const &message)
std::size_t numDownVotedAmendments()
Amendments that this server won't vote for by default.
std::map< std::string, VoteBehavior > const & supportedAmendments()
Amendments that this server supports and the default voting behavior.
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
static constexpr std::size_t numFeatures
Definition Feature.h:137
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
uint256 registerFeature(std::string const &name, Supported support, VoteBehavior vote)
consteval auto validFeatureNameSize(auto fn) -> bool
Definition Feature.h:80
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:204
uint256 retireFeature(std::string const &name)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
VoteBehavior
Definition Feature.h:110
static bool const readOnlySet
size_t featureToBitsetIndex(uint256 const &f)
bool registrationIsDone()
Tell FeatureCollections when registration is complete.
base_uint< 256 > uint256
Definition base_uint.h:531
consteval auto enforceValidFeatureName(auto fn) -> char const *
std::map< std::string, AmendmentSupport > const & allAmendments()
All amendments libxrpl knows about.
std::string featureToName(uint256 const &f)
uint256 bitsetIndexToFeature(size_t i)
consteval auto validFeatureName(auto fn) -> bool
Definition Feature.h:95
@ no
Definition Steps.h:24
@ yes
Definition Steps.h:24
std::optional< uint256 > getRegisteredFeature(std::string const &name)
std::size_t hash_value(xrpl::uint256 const &feature)
T size(T... args)