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