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
160 bool
162
163 std::size_t
164 featureToBitsetIndex(uint256 const& f) const;
165
166 uint256 const&
167 bitsetIndexToFeature(size_t i) const;
168
169 std::string
170 featureToName(uint256 const& f) const;
171
175 std::map<std::string, AmendmentSupport> const&
176 allAmendments() const
177 {
178 return all_;
179 }
180
186 std::map<std::string, VoteBehavior> const&
187 supportedAmendments() const
188 {
189 return supported_;
190 }
191
195 std::size_t
197 {
198 return downVotes_;
199 }
200
204 std::size_t
206 {
207 return upVotes_;
208 }
209};
210
211//------------------------------------------------------------------------------
212
213FeatureCollections::FeatureCollections()
214{
215 features_.reserve(xrpl::detail::kNumFeatures);
216}
217
218std::optional<uint256>
219FeatureCollections::getRegisteredFeature(std::string const& name) const
220{
221 XRPL_ASSERT(
222 readOnly_.load(), "xrpl::FeatureCollections::getRegisteredFeature : startup completed");
223 Feature const* feature = getByName(name);
224 if (feature != nullptr)
225 return feature->feature;
226 return std::nullopt;
227}
228
229void
230check(bool condition, char const* logicErrorMessage)
231{
232 if (!condition)
233 logicError(logicErrorMessage);
234}
235
237FeatureCollections::registerFeature(std::string const& name, Supported support, VoteBehavior vote)
238{
239 check(!readOnly_, "Attempting to register a feature after startup.");
240 check(
241 support == Supported::Yes || vote == VoteBehavior::DefaultNo,
242 "Invalid feature parameters. Must be supported to be up-voted.");
243 Feature const* i = getByName(name);
244 if (i == nullptr)
245 {
246 check(features_.size() < detail::kNumFeatures, "More features defined than allocated.");
247
248 auto const f = sha512Half(Slice(name.data(), name.size()));
249
250 features_.emplace_back(name, f);
251
252 auto const getAmendmentSupport = [=]() {
253 if (vote == VoteBehavior::Obsolete)
254 return AmendmentSupport::Retired;
255 return support == Supported::Yes ? AmendmentSupport::Supported
256 : AmendmentSupport::Unsupported;
257 };
258 all_.emplace(name, getAmendmentSupport());
259
260 if (support == Supported::Yes)
261 {
262 supported_.emplace(name, vote);
263
264 if (vote == VoteBehavior::DefaultYes)
265 {
266 ++upVotes_;
267 }
268 else
269 {
270 ++downVotes_;
271 }
272 }
273 check(upVotes_ + downVotes_ == supported_.size(), "Feature counting logic broke");
274 check(
275 supported_.size() <= features_.size(), "More supported features than defined features");
276 check(features_.size() == all_.size(), "The 'all' features list is populated incorrectly");
277 return f;
278 }
279
280 // Each feature should only be registered once
281 logicError("Duplicate feature registration");
282}
283
287bool
288FeatureCollections::registrationIsDone()
289{
290 readOnly_ = true;
291 return true;
292}
293
294size_t
295FeatureCollections::featureToBitsetIndex(uint256 const& f) const
296{
297 XRPL_ASSERT(
298 readOnly_.load(), "xrpl::FeatureCollections::featureToBitsetIndex : startup completed");
299
300 Feature const* feature = getByFeature(f);
301 if (feature == nullptr)
302 logicError("Invalid Feature ID");
303
304 return getIndex(*feature);
305}
306
307uint256 const&
308FeatureCollections::bitsetIndexToFeature(size_t i) const
309{
310 XRPL_ASSERT(
311 readOnly_.load(), "xrpl::FeatureCollections::bitsetIndexToFeature : startup completed");
312 Feature const& feature = getByIndex(i);
313 return feature.feature;
314}
315
316std::string
317FeatureCollections::featureToName(uint256 const& f) const
318{
319 XRPL_ASSERT(readOnly_.load(), "xrpl::FeatureCollections::featureToName : startup completed");
320 Feature const* feature = getByFeature(f);
321 return (feature != nullptr) ? feature->name : to_string(f);
322}
323
324FeatureCollections gFeatureCollections;
325
326} // namespace
327
331std::map<std::string, AmendmentSupport> const&
333{
334 return gFeatureCollections.allAmendments();
335}
336
344{
345 return gFeatureCollections.supportedAmendments();
346}
347
353{
354 return gFeatureCollections.numDownVotedAmendments();
355}
356
362{
363 return gFeatureCollections.numUpVotedAmendments();
364}
365
366//------------------------------------------------------------------------------
367
370{
371 return gFeatureCollections.getRegisteredFeature(name);
372}
373
375registerFeature(std::string const& name, Supported support, VoteBehavior vote)
376{
377 return gFeatureCollections.registerFeature(name, support, vote);
378}
379
380// Retired features are in the ledger and have no code controlled by the
381// feature. They need to be supported, but do not need to be voted on.
384{
385 return registerFeature(name, Supported::Yes, VoteBehavior::Obsolete);
386}
387
391bool
393{
394 return gFeatureCollections.registrationIsDone();
395}
396
397size_t
399{
400 return gFeatureCollections.featureToBitsetIndex(f);
401}
402
405{
406 return gFeatureCollections.bitsetIndexToFeature(i);
407}
408
411{
412 return gFeatureCollections.featureToName(f);
413}
414
415// All known amendments must be registered either here or below with the
416// "retired" amendments
417
418#pragma push_macro("XRPL_FEATURE")
419#undef XRPL_FEATURE
420#pragma push_macro("XRPL_FIX")
421#undef XRPL_FIX
422#pragma push_macro("XRPL_RETIRE_FEATURE")
423#undef XRPL_RETIRE_FEATURE
424#pragma push_macro("XRPL_RETIRE_FIX")
425#undef XRPL_RETIRE_FIX
426
427consteval auto
428enforceValidFeatureName(auto fn) -> char const*
429{
430 static_assert(validFeatureName(fn), "Invalid feature name");
431 static_assert(validFeatureNameSize(fn), "Invalid feature name size");
432 return fn();
433}
434
435#define XRPL_FEATURE(name, supported, vote) \
436 uint256 const feature##name = \
437 registerFeature(enforceValidFeatureName([] { return #name; }), supported, vote);
438#define XRPL_FIX(name, supported, vote) \
439 uint256 const fix##name = \
440 registerFeature(enforceValidFeatureName([] { return "fix" #name; }), supported, vote);
441
442// clang-format off
443#define XRPL_RETIRE_FEATURE(name) \
444 [[deprecated("The referenced feature amendment has been retired")]] \
445 [[maybe_unused]] \
446 uint256 const retiredFeature##name = retireFeature(#name);
447
448#define XRPL_RETIRE_FIX(name) \
449 [[deprecated("The referenced fix amendment has been retired")]] \
450 [[maybe_unused]] \
451 uint256 const retiredFix##name = retireFeature("fix" #name);
452// clang-format on
453
454#include <xrpl/protocol/detail/features.macro>
455
456#include <utility>
457
458#undef XRPL_RETIRE_FEATURE
459#pragma pop_macro("XRPL_RETIRE_FEATURE")
460#undef XRPL_RETIRE_FIX
461#pragma pop_macro("XRPL_RETIRE_FIX")
462#undef XRPL_FIX
463#pragma pop_macro("XRPL_FIX")
464#undef XRPL_FEATURE
465#pragma pop_macro("XRPL_FEATURE")
466
467// All of the features should now be registered, since variables in a cpp file
468// are initialized from top to bottom.
469//
470// Use initialization of one final static variable to set featureCollections::readOnly_.
471[[maybe_unused]] static bool const kReadOnlySet = gFeatureCollections.registrationIsDone();
472
473} // 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:143
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:82
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:215
uint256 retireFeature(std::string const &name)
VoteBehavior
Definition Feature.h:112
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.
@ Yes
We have consensus along with the network.
@ No
We do not have consensus.
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:97
std::optional< uint256 > getRegisteredFeature(std::string const &name)
BaseUInt< 256 > uint256
Definition base_uint.h:580
std::size_t hash_value(xrpl::uint256 const &feature)
T size(T... args)
T to_string(T... args)