rippled
Feature.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/protocol/Feature.h>
21 
22 #include <ripple/basics/Slice.h>
23 #include <ripple/basics/contract.h>
24 #include <ripple/protocol/digest.h>
25 #include <boost/container_hash/hash.hpp>
26 #include <boost/multi_index/hashed_index.hpp>
27 #include <boost/multi_index/key_extractors.hpp>
28 #include <boost/multi_index/random_access_index.hpp>
29 #include <boost/multi_index_container.hpp>
30 #include <cstring>
31 
32 namespace ripple {
33 
34 inline std::size_t
35 hash_value(ripple::uint256 const& feature)
36 {
37  std::size_t seed = 0;
38  using namespace boost;
39  for (auto const& n : feature)
40  hash_combine(seed, n);
41  return seed;
42 }
43 
44 namespace {
45 
46 enum class Supported : bool { no = false, yes };
47 
48 // *NOTE*
49 //
50 // Features, or Amendments as they are called elsewhere, are enabled on the
51 // network at some specific time based on Validator voting. Features are
52 // enabled using run-time conditionals based on the state of the amendment.
53 // There is value in retaining that conditional code for some time after
54 // the amendment is enabled to make it simple to replay old transactions.
55 // However, once an amendment has been enabled for, say, more than two years
56 // then retaining that conditional code has less value since it is
57 // uncommon to replay such old transactions.
58 //
59 // Starting in January of 2020 Amendment conditionals from before January
60 // 2018 are being removed. So replaying any ledger from before January
61 // 2018 needs to happen on an older version of the server code. There's
62 // a log message in Application.cpp that warns about replaying old ledgers.
63 //
64 // At some point in the future someone may wish to remove amendment
65 // conditional code for amendments that were enabled after January 2018.
66 // When that happens then the log message in Application.cpp should be
67 // updated.
68 //
69 // Generally, amendments which introduce new features should be set as
70 // "VoteBehavior::DefaultNo" whereas in rare cases, amendments that fix
71 // critical bugs should be set as "VoteBehavior::DefaultYes", if off-chain
72 // consensus is reached amongst reviewers, validator operators, and other
73 // participants.
74 
75 class FeatureCollections
76 {
77  struct Feature
78  {
79  std::string name;
80  uint256 feature;
81 
82  Feature() = delete;
83  explicit Feature(std::string const& name_, uint256 const& feature_)
84  : name(name_), feature(feature_)
85  {
86  }
87 
88  // These structs are used by the `features` multi_index_container to
89  // provide access to the features collection by size_t index, string
90  // name, and uint256 feature identifier
91  struct byIndex
92  {
93  };
94  struct byName
95  {
96  };
97  struct byFeature
98  {
99  };
100  };
101 
102  // Intermediate types to help with readability
103  template <class tag, typename Type, Type Feature::*PtrToMember>
104  using feature_hashed_unique = boost::multi_index::hashed_unique<
105  boost::multi_index::tag<tag>,
106  boost::multi_index::member<Feature, Type, PtrToMember>>;
107 
108  // Intermediate types to help with readability
109  using feature_indexing = boost::multi_index::indexed_by<
110  boost::multi_index::random_access<
111  boost::multi_index::tag<Feature::byIndex>>,
112  feature_hashed_unique<Feature::byFeature, uint256, &Feature::feature>,
113  feature_hashed_unique<Feature::byName, std::string, &Feature::name>>;
114 
115  // This multi_index_container provides access to the features collection by
116  // name, index, and uint256 feature identifier
117  boost::multi_index::multi_index_container<Feature, feature_indexing>
118  features;
120  std::size_t upVotes = 0;
121  std::size_t downVotes = 0;
122  mutable std::atomic<bool> readOnly = false;
123 
124  // These helper functions provide access to the features collection by name,
125  // index, and uint256 feature identifier, so the details of
126  // multi_index_container can be hidden
127  Feature const&
128  getByIndex(size_t i) const
129  {
130  if (i >= features.size())
131  LogicError("Invalid FeatureBitset index");
132  const auto& sequence = features.get<Feature::byIndex>();
133  return sequence[i];
134  }
135  size_t
136  getIndex(Feature const& feature) const
137  {
138  const auto& sequence = features.get<Feature::byIndex>();
139  auto const it_to = sequence.iterator_to(feature);
140  return it_to - sequence.begin();
141  }
142  Feature const*
143  getByFeature(uint256 const& feature) const
144  {
145  const auto& feature_index = features.get<Feature::byFeature>();
146  auto const feature_it = feature_index.find(feature);
147  return feature_it == feature_index.end() ? nullptr : &*feature_it;
148  }
149  Feature const*
150  getByName(std::string const& name) const
151  {
152  const auto& name_index = features.get<Feature::byName>();
153  auto const name_it = name_index.find(name);
154  return name_it == name_index.end() ? nullptr : &*name_it;
155  }
156 
157 public:
158  FeatureCollections();
159 
161  getRegisteredFeature(std::string const& name) const;
162 
163  uint256
165  std::string const& name,
166  Supported support,
167  VoteBehavior vote);
168 
170  bool
172 
174  featureToBitsetIndex(uint256 const& f) const;
175 
176  uint256 const&
177  bitsetIndexToFeature(size_t i) const;
178 
180  featureToName(uint256 const& f) const;
181 
186  supportedAmendments() const
187  {
188  return supported;
189  }
190 
193  numDownVotedAmendments() const
194  {
195  return downVotes;
196  }
197 
200  numUpVotedAmendments() const
201  {
202  return upVotes;
203  }
204 };
205 
206 //------------------------------------------------------------------------------
207 
208 FeatureCollections::FeatureCollections()
209 {
210  features.reserve(ripple::detail::numFeatures);
211 }
212 
214 FeatureCollections::getRegisteredFeature(std::string const& name) const
215 {
216  assert(readOnly);
217  Feature const* feature = getByName(name);
218  if (feature)
219  return feature->feature;
220  return std::nullopt;
221 }
222 
223 void
224 check(bool condition, const char* logicErrorMessage)
225 {
226  if (!condition)
227  LogicError(logicErrorMessage);
228 }
229 
230 uint256
231 FeatureCollections::registerFeature(
232  std::string const& name,
233  Supported support,
234  VoteBehavior vote)
235 {
236  check(!readOnly, "Attempting to register a feature after startup.");
237  check(
238  support == Supported::yes || vote == VoteBehavior::DefaultNo,
239  "Invalid feature parameters. Must be supported to be up-voted.");
240  Feature const* i = getByName(name);
241  if (!i)
242  {
243  // If this check fails, and you just added a feature, increase the
244  // numFeatures value in Feature.h
245  check(
246  features.size() < detail::numFeatures,
247  "More features defined than allocated. Adjust numFeatures in "
248  "Feature.h.");
249 
250  auto const f = sha512Half(Slice(name.data(), name.size()));
251 
252  features.emplace_back(name, f);
253 
254  if (support == Supported::yes)
255  {
256  supported.emplace(name, vote);
257 
258  if (vote == VoteBehavior::DefaultYes)
259  ++upVotes;
260  else
261  ++downVotes;
262  }
263  check(
264  upVotes + downVotes == supported.size(),
265  "Feature counting logic broke");
266  check(
267  supported.size() <= features.size(),
268  "More supported features than defined features");
269  return f;
270  }
271  else
272  // Each feature should only be registered once
273  LogicError("Duplicate feature registration");
274 }
275 
277 bool
278 FeatureCollections::registrationIsDone()
279 {
280  readOnly = true;
281  return true;
282 }
283 
284 size_t
285 FeatureCollections::featureToBitsetIndex(uint256 const& f) const
286 {
287  assert(readOnly);
288 
289  Feature const* feature = getByFeature(f);
290  if (!feature)
291  LogicError("Invalid Feature ID");
292 
293  return getIndex(*feature);
294 }
295 
296 uint256 const&
297 FeatureCollections::bitsetIndexToFeature(size_t i) const
298 {
299  assert(readOnly);
300  Feature const& feature = getByIndex(i);
301  return feature.feature;
302 }
303 
305 FeatureCollections::featureToName(uint256 const& f) const
306 {
307  assert(readOnly);
308  Feature const* feature = getByFeature(f);
309  return feature ? feature->name : to_string(f);
310 }
311 
312 static FeatureCollections featureCollections;
313 
314 } // namespace
315 
321 {
322  return featureCollections.supportedAmendments();
323 }
324 
328 {
329  return featureCollections.numDownVotedAmendments();
330 }
331 
335 {
336  return featureCollections.numUpVotedAmendments();
337 }
338 
339 //------------------------------------------------------------------------------
340 
343 {
344  return featureCollections.getRegisteredFeature(name);
345 }
346 
347 uint256
348 registerFeature(std::string const& name, Supported support, VoteBehavior vote)
349 {
350  return featureCollections.registerFeature(name, support, vote);
351 }
352 
353 // Retired features are in the ledger and have no code controlled by the
354 // feature. They need to be supported, but do not need to be voted on.
355 uint256
357 {
358  return registerFeature(name, Supported::yes, VoteBehavior::Obsolete);
359 }
360 
362 bool
364 {
365  return featureCollections.registrationIsDone();
366 }
367 
368 size_t
370 {
371  return featureCollections.featureToBitsetIndex(f);
372 }
373 
374 uint256
376 {
377  return featureCollections.bitsetIndexToFeature(i);
378 }
379 
382 {
383  return featureCollections.featureToName(f);
384 }
385 
386 #pragma push_macro("REGISTER_FEATURE")
387 #undef REGISTER_FEATURE
388 
394 #define REGISTER_FEATURE(fName, supported, votebehavior) \
395  uint256 const feature##fName = \
396  registerFeature(#fName, supported, votebehavior)
397 
398 #pragma push_macro("REGISTER_FIX")
399 #undef REGISTER_FIX
400 
406 #define REGISTER_FIX(fName, supported, votebehavior) \
407  uint256 const fName = registerFeature(#fName, supported, votebehavior)
408 
409 // clang-format off
410 
411 // All known amendments must be registered either here or below with the
412 // "retired" amendments
413 REGISTER_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo);
414 REGISTER_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes);
415 REGISTER_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes);
417 REGISTER_FEATURE(DepositAuth, Supported::yes, VoteBehavior::DefaultYes);
418 REGISTER_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes);
422 REGISTER_FEATURE(DepositPreauth, Supported::yes, VoteBehavior::DefaultYes);
423 // Use liquidity from strands that consume max offers, but mark as dry
426 REGISTER_FEATURE(MultiSignReserve, Supported::yes, VoteBehavior::DefaultYes);
431 REGISTER_FEATURE(DeletableAccounts, Supported::yes, VoteBehavior::DefaultYes);
432 // fixQualityUpperBound should be activated before FlowCross
434 REGISTER_FEATURE(RequireFullyCanonicalSig, Supported::yes, VoteBehavior::DefaultYes);
435 // fix1781: XRPEndpointSteps should be included in the circular payment check
437 REGISTER_FEATURE(HardenedValidations, Supported::yes, VoteBehavior::DefaultYes);
439 REGISTER_FEATURE(NegativeUNL, Supported::yes, VoteBehavior::DefaultYes);
440 REGISTER_FEATURE(TicketBatch, Supported::yes, VoteBehavior::DefaultYes);
441 REGISTER_FEATURE(FlowSortStrands, Supported::yes, VoteBehavior::DefaultYes);
444 REGISTER_FEATURE(CheckCashMakesTrustLine, Supported::yes, VoteBehavior::DefaultNo);
445 REGISTER_FEATURE(ExpandedSignerList, Supported::yes, VoteBehavior::DefaultNo);
446 REGISTER_FEATURE(NonFungibleTokensV1_1, Supported::yes, VoteBehavior::DefaultNo);
449 REGISTER_FEATURE(ImmediateOfferKilled, Supported::yes, VoteBehavior::DefaultNo);
450 REGISTER_FEATURE(DisallowIncoming, Supported::yes, VoteBehavior::DefaultNo);
451 REGISTER_FEATURE(XRPFees, Supported::yes, VoteBehavior::DefaultNo);
455 
456 // The following amendments are obsolete, but must remain supported
457 // because they could potentially get enabled.
458 //
459 // Obsolete features are (usually) not in the ledger, and may have code
460 // controlled by the feature. They need to be supported because at some
461 // time in the past, the feature was supported and votable, but never
462 // passed. So the feature needs to be supported in case it is ever
463 // enabled (added to the ledger).
464 //
465 // If a feature remains obsolete for long enough that no clients are able
466 // to vote for it, the feature can be removed (entirely?) from the code.
467 REGISTER_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete);
468 REGISTER_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete);
471 
472 // The following amendments have been active for at least two years. Their
473 // pre-amendment code has been removed and the identifiers are deprecated.
474 // All known amendments and amendments that may appear in a validated
475 // ledger must be registered either here or above with the "active" amendments
476 [[deprecated("The referenced amendment has been retired"), maybe_unused]]
477 uint256 const
482  retiredCryptoConditions = retireFeature("CryptoConditions"),
487  retiredEnforceInvariants = retireFeature("EnforceInvariants"),
488  retiredSortedDirectories = retireFeature("SortedDirectories"),
493 
494 // clang-format on
495 
496 #undef REGISTER_FIX
497 #pragma pop_macro("REGISTER_FIX")
498 
499 #undef REGISTER_FEATURE
500 #pragma pop_macro("REGISTER_FEATURE")
501 
502 // All of the features should now be registered, since variables in a cpp file
503 // are initialized from top to bottom.
504 //
505 // Use initialization of one final static variable to set
506 // featureCollections::readOnly.
507 [[maybe_unused]] static const bool readOnlySet =
508  featureCollections.registrationIsDone();
509 
510 } // namespace ripple
ripple::fixRemoveNFTokenAutoTrustLine
const uint256 fixRemoveNFTokenAutoTrustLine
ripple::REGISTER_FIX
REGISTER_FIX(fix1513, Supported::yes, VoteBehavior::DefaultYes)
ripple::fixQualityUpperBound
const uint256 fixQualityUpperBound
ripple::fixNFTokenNegOffer
const uint256 fixNFTokenNegOffer
std::string
STL class.
cstring
ripple::retiredFix1528
const uint256 retiredFix1528
Definition: Feature.cpp:492
ripple::retiredSortedDirectories
const uint256 retiredSortedDirectories
Definition: Feature.cpp:488
ripple::fix1515
const uint256 fix1515
ripple::detail::numFeatures
static constexpr std::size_t numFeatures
Definition: Feature.h:77
ripple::hash_value
std::size_t hash_value(ripple::uint256 const &feature)
Definition: Feature.cpp:35
std::string::size
T size(T... args)
ripple::fixSTAmountCanonicalize
const uint256 fixSTAmountCanonicalize
ripple::VoteBehavior::DefaultNo
@ DefaultNo
ripple::fix1781
const uint256 fix1781
Json::check
void check(bool condition, std::string const &message)
Definition: json/Writer.h:252
ripple::readOnlySet
static const bool readOnlySet
Definition: Feature.cpp:507
ripple::retiredPayChan
const uint256 retiredPayChan
Definition: Feature.cpp:481
boost
Definition: IPAddress.h:103
ripple::retiredFix1368
const uint256 retiredFix1368
Definition: Feature.cpp:484
ripple::retiredEnforceInvariants
const uint256 retiredEnforceInvariants
Definition: Feature.cpp:487
ripple::retiredMultiSign
const uint256 retiredMultiSign
Definition: Feature.cpp:478
ripple::VoteBehavior::Obsolete
@ Obsolete
ripple::REGISTER_FEATURE
REGISTER_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo)
ripple::fixNFTokenRemint
const uint256 fixNFTokenRemint
ripple::uint256
base_uint< 256 > uint256
Definition: base_uint.h:550
ripple::retiredTickSize
const uint256 retiredTickSize
Definition: Feature.cpp:483
ripple::detail::numUpVotedAmendments
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition: Feature.cpp:334
ripple::base_uint< 256 >
ripple::detail::numDownVotedAmendments
std::size_t numDownVotedAmendments()
Amendments that this server won't vote for by default.
Definition: Feature.cpp:327
ripple::retiredEscrow
const uint256 retiredEscrow
Definition: Feature.cpp:485
ripple::VoteBehavior
VoteBehavior
Definition: Feature.h:69
ripple::fix1513
const uint256 fix1513
ripple::fixCheckThreading
const uint256 fixCheckThreading
ripple::retiredFix1373
const uint256 retiredFix1373
Definition: Feature.cpp:486
ripple::fixAmendmentMajorityCalc
const uint256 fixAmendmentMajorityCalc
ripple::retiredTrustSetAuth
const uint256 retiredTrustSetAuth
Definition: Feature.cpp:479
ripple::fixTakerDryOfferRemoval
const uint256 fixTakerDryOfferRemoval
ripple::fix1623
const uint256 fix1623
ripple::featureToBitsetIndex
size_t featureToBitsetIndex(uint256 const &f)
Definition: Feature.cpp:369
ripple::fixPayChanRecipientOwnerDir
const uint256 fixPayChanRecipientOwnerDir
ripple::fixMasterKeyAsRegularKey
const uint256 fixMasterKeyAsRegularKey
std::atomic< bool >
ripple::retiredCryptoConditions
const uint256 retiredCryptoConditions
Definition: Feature.cpp:482
std::map
STL class.
ripple::fixNFTokenDirV1
const uint256 fixNFTokenDirV1
ripple::fixRmSmallIncreasedQOffers
const uint256 fixRmSmallIncreasedQOffers
ripple::fixTrustLinesToSelf
const uint256 fixTrustLinesToSelf
ripple::fix1543
const uint256 fix1543
ripple::retiredFeeEscalation
const uint256 retiredFeeEscalation
Definition: Feature.cpp:480
ripple::fix1578
const uint256 fix1578
ripple::registerFeature
uint256 registerFeature(std::string const &name, Supported support, VoteBehavior vote)
Definition: Feature.cpp:348
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::LogicError
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:48
ripple::sha512Half
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition: digest.h:216
ripple::fixUniversalNumber
const uint256 fixUniversalNumber
ripple::bitsetIndexToFeature
uint256 bitsetIndexToFeature(size_t i)
Definition: Feature.cpp:375
ripple::retiredFix1523
const uint256 retiredFix1523
Definition: Feature.cpp:491
ripple::retiredFix1201
const uint256 retiredFix1201
Definition: Feature.cpp:489
std::optional
std::size_t
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::retireFeature
uint256 retireFeature(std::string const &name)
Definition: Feature.cpp:356
ripple::fixNonFungibleTokensV1_2
const uint256 fixNonFungibleTokensV1_2
ripple::retiredFix1512
const uint256 retiredFix1512
Definition: Feature.cpp:490
ripple::fix1571
const uint256 fix1571
ripple::getRegisteredFeature
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:342
ripple::registrationIsDone
bool registrationIsDone()
Tell FeatureCollections when registration is complete.
Definition: Feature.cpp:363
std::string::data
T data(T... args)
ripple::detail::supportedAmendments
std::map< std::string, VoteBehavior > const & supportedAmendments()
Amendments that this server supports and the default voting behavior.
Definition: Feature.cpp:320
ripple::featureToName
std::string featureToName(uint256 const &f)
Definition: Feature.cpp:381
ripple::VoteBehavior::DefaultYes
@ DefaultYes