rippled
Feature_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2017 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/app/misc/AmendmentTable.h>
21 #include <ripple/protocol/Feature.h>
22 #include <ripple/protocol/jss.h>
23 #include <test/jtx.h>
24 
25 namespace ripple {
26 
27 class Feature_test : public beast::unit_test::suite
28 {
29  void
31  {
32  testcase("internals");
33 
34  std::map<std::string, VoteBehavior> const& supported =
36  BEAST_EXPECT(
37  supported.size() ==
40  std::size_t up = 0, down = 0, obsolete = 0;
41  for (std::pair<std::string const, VoteBehavior> const& amendment :
42  supported)
43  {
44  switch (amendment.second)
45  {
47  ++up;
48  break;
50  ++down;
51  break;
53  ++obsolete;
54  break;
55  default:
56  fail("Unknown VoteBehavior", __FILE__, __LINE__);
57  }
58  }
59  BEAST_EXPECT(
61  BEAST_EXPECT(up == ripple::detail::numUpVotedAmendments());
62  }
63 
64  void
66  {
67  testcase("featureToName");
68 
69  // Test all the supported features. In a perfect world, this would test
70  // FeatureCollections::featureNames, but that's private. Leave it that
71  // way.
72  auto const supported = ripple::detail::supportedAmendments();
73 
74  for (auto const& [feature, vote] : supported)
75  {
76  (void)vote;
77  auto const registered = getRegisteredFeature(feature);
78  if (BEAST_EXPECT(registered))
79  {
80  BEAST_EXPECT(featureToName(*registered) == feature);
81  BEAST_EXPECT(
83  *registered);
84  }
85  }
86 
87  // Test an arbitrary unknown feature
88  uint256 zero{0};
89  BEAST_EXPECT(featureToName(zero) == to_string(zero));
90  BEAST_EXPECT(
91  featureToName(zero) ==
92  "0000000000000000000000000000000000000000000000000000000000000000");
93 
94  // Test looking up an unknown feature
95  BEAST_EXPECT(!getRegisteredFeature("unknown"));
96 
97  // Test a random sampling of the variables. If any of these get retired
98  // or removed, swap out for any other feature.
99  BEAST_EXPECT(featureToName(featureOwnerPaysFee) == "OwnerPaysFee");
100  BEAST_EXPECT(featureToName(featureFlow) == "Flow");
101  BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
102  BEAST_EXPECT(featureToName(fix1578) == "fix1578");
103  BEAST_EXPECT(
105  "fixTakerDryOfferRemoval");
106  }
107 
108  void
110  {
111  testcase("No Params, None Enabled");
112 
113  using namespace test::jtx;
114  Env env{*this};
115 
118 
119  auto jrr = env.rpc("feature")[jss::result];
120  if (!BEAST_EXPECT(jrr.isMember(jss::features)))
121  return;
122  for (auto const& feature : jrr[jss::features])
123  {
124  if (!BEAST_EXPECT(feature.isMember(jss::name)))
125  return;
126  // default config - so all should be disabled, and
127  // supported. Some may be vetoed.
128  bool expectVeto =
129  (votes.at(feature[jss::name].asString()) ==
131  bool expectObsolete =
132  (votes.at(feature[jss::name].asString()) ==
134  BEAST_EXPECTS(
135  feature.isMember(jss::enabled) &&
136  !feature[jss::enabled].asBool(),
137  feature[jss::name].asString() + " enabled");
138  BEAST_EXPECTS(
139  feature.isMember(jss::vetoed) &&
140  feature[jss::vetoed].isBool() == !expectObsolete &&
141  (!feature[jss::vetoed].isBool() ||
142  feature[jss::vetoed].asBool() == expectVeto) &&
143  (feature[jss::vetoed].isBool() ||
144  feature[jss::vetoed].asString() == "Obsolete"),
145  feature[jss::name].asString() + " vetoed");
146  BEAST_EXPECTS(
147  feature.isMember(jss::supported) &&
148  feature[jss::supported].asBool(),
149  feature[jss::name].asString() + " supported");
150  }
151  }
152 
153  void
155  {
156  testcase("Feature Param");
157 
158  using namespace test::jtx;
159  Env env{*this};
160 
161  auto jrr = env.rpc("feature", "MultiSignReserve")[jss::result];
162  BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
163  jrr.removeMember(jss::status);
164  BEAST_EXPECT(jrr.size() == 1);
165  BEAST_EXPECT(
166  jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
167  "1FC07EFE41D"));
168  auto feature = *(jrr.begin());
169 
170  BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
171  BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
172  BEAST_EXPECTS(
173  feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
174  "vetoed");
175  BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
176 
177  // feature names are case-sensitive - expect error here
178  jrr = env.rpc("feature", "multiSignReserve")[jss::result];
179  BEAST_EXPECT(jrr[jss::error] == "badFeature");
180  BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
181  }
182 
183  void
185  {
186  testcase("Invalid Feature");
187 
188  using namespace test::jtx;
189  Env env{*this};
190 
191  auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
192  BEAST_EXPECT(jrr[jss::error] == "badFeature");
193  BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
194  }
195 
196  void
198  {
199  testcase("Feature Without Admin");
200 
201  using namespace test::jtx;
202  Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
203  (*cfg)["port_rpc"].set("admin", "");
204  (*cfg)["port_ws"].set("admin", "");
205  return cfg;
206  })};
207 
208  auto jrr = env.rpc("feature")[jss::result];
209  // The current HTTP/S ServerHandler returns an HTTP 403 error code here
210  // rather than a noPermission JSON error. The JSONRPCClient just eats
211  // that error and returns an null result.
212  BEAST_EXPECT(jrr.isNull());
213  }
214 
215  void
217  {
218  testcase("No Params, Some Enabled");
219 
220  using namespace test::jtx;
221  Env env{
223 
226 
227  auto jrr = env.rpc("feature")[jss::result];
228  if (!BEAST_EXPECT(jrr.isMember(jss::features)))
229  return;
230  for (auto it = jrr[jss::features].begin();
231  it != jrr[jss::features].end();
232  ++it)
233  {
234  uint256 id;
235  (void)id.parseHex(it.key().asString().c_str());
236  if (!BEAST_EXPECT((*it).isMember(jss::name)))
237  return;
238  bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
239  bool expectSupported =
240  env.app().getAmendmentTable().isSupported(id);
241  bool expectVeto =
242  (votes.at((*it)[jss::name].asString()) ==
244  bool expectObsolete =
245  (votes.at((*it)[jss::name].asString()) ==
247  BEAST_EXPECTS(
248  (*it).isMember(jss::enabled) &&
249  (*it)[jss::enabled].asBool() == expectEnabled,
250  (*it)[jss::name].asString() + " enabled");
251  if (expectEnabled)
252  BEAST_EXPECTS(
253  !(*it).isMember(jss::vetoed),
254  (*it)[jss::name].asString() + " vetoed");
255  else
256  BEAST_EXPECTS(
257  (*it).isMember(jss::vetoed) &&
258  (*it)[jss::vetoed].isBool() == !expectObsolete &&
259  (!(*it)[jss::vetoed].isBool() ||
260  (*it)[jss::vetoed].asBool() == expectVeto) &&
261  ((*it)[jss::vetoed].isBool() ||
262  (*it)[jss::vetoed].asString() == "Obsolete"),
263  (*it)[jss::name].asString() + " vetoed");
264  BEAST_EXPECTS(
265  (*it).isMember(jss::supported) &&
266  (*it)[jss::supported].asBool() == expectSupported,
267  (*it)[jss::name].asString() + " supported");
268  }
269  }
270 
271  void
273  {
274  testcase("With Majorities");
275 
276  using namespace test::jtx;
277  Env env{*this, envconfig(validator, "")};
278 
279  auto jrr = env.rpc("feature")[jss::result];
280  if (!BEAST_EXPECT(jrr.isMember(jss::features)))
281  return;
282 
283  // at this point, there are no majorities so no fields related to
284  // amendment voting
285  for (auto const& feature : jrr[jss::features])
286  {
287  if (!BEAST_EXPECT(feature.isMember(jss::name)))
288  return;
289  BEAST_EXPECTS(
290  !feature.isMember(jss::majority),
291  feature[jss::name].asString() + " majority");
292  BEAST_EXPECTS(
293  !feature.isMember(jss::count),
294  feature[jss::name].asString() + " count");
295  BEAST_EXPECTS(
296  !feature.isMember(jss::threshold),
297  feature[jss::name].asString() + " threshold");
298  BEAST_EXPECTS(
299  !feature.isMember(jss::validations),
300  feature[jss::name].asString() + " validations");
301  BEAST_EXPECTS(
302  !feature.isMember(jss::vote),
303  feature[jss::name].asString() + " vote");
304  }
305 
306  auto majorities = getMajorityAmendments(*env.closed());
307  if (!BEAST_EXPECT(majorities.empty()))
308  return;
309 
310  // close ledgers until the amendments show up.
311  for (auto i = 0; i <= 256; ++i)
312  {
313  env.close();
314  majorities = getMajorityAmendments(*env.closed());
315  if (!majorities.empty())
316  break;
317  }
318 
319  // There should be at least 5 amendments. Don't do exact comparison
320  // to avoid maintenance as more amendments are added in the future.
321  BEAST_EXPECT(majorities.size() >= 5);
324 
325  jrr = env.rpc("feature")[jss::result];
326  if (!BEAST_EXPECT(jrr.isMember(jss::features)))
327  return;
328  for (auto const& feature : jrr[jss::features])
329  {
330  if (!BEAST_EXPECT(feature.isMember(jss::name)))
331  return;
332  bool expectVeto =
333  (votes.at(feature[jss::name].asString()) ==
335  bool expectObsolete =
336  (votes.at(feature[jss::name].asString()) ==
338  BEAST_EXPECTS(
339  (expectVeto || expectObsolete) ^
340  feature.isMember(jss::majority),
341  feature[jss::name].asString() + " majority");
342  BEAST_EXPECTS(
343  feature.isMember(jss::vetoed) &&
344  feature[jss::vetoed].isBool() == !expectObsolete &&
345  (!feature[jss::vetoed].isBool() ||
346  feature[jss::vetoed].asBool() == expectVeto) &&
347  (feature[jss::vetoed].isBool() ||
348  feature[jss::vetoed].asString() == "Obsolete"),
349  feature[jss::name].asString() + " vetoed");
350  BEAST_EXPECTS(
351  feature.isMember(jss::count),
352  feature[jss::name].asString() + " count");
353  BEAST_EXPECTS(
354  feature.isMember(jss::threshold),
355  feature[jss::name].asString() + " threshold");
356  BEAST_EXPECTS(
357  feature.isMember(jss::validations),
358  feature[jss::name].asString() + " validations");
359  BEAST_EXPECT(
360  feature[jss::count] ==
361  ((expectVeto || expectObsolete) ? 0 : 1));
362  BEAST_EXPECT(feature[jss::threshold] == 1);
363  BEAST_EXPECT(feature[jss::validations] == 1);
364  BEAST_EXPECTS(
365  expectVeto || expectObsolete || feature[jss::majority] == 2540,
366  "Majority: " + feature[jss::majority].asString());
367  }
368  }
369 
370  void
372  {
373  testcase("Veto");
374 
375  using namespace test::jtx;
376  Env env{*this, FeatureBitset(featureMultiSignReserve)};
377  constexpr const char* featureName = "MultiSignReserve";
378 
379  auto jrr = env.rpc("feature", featureName)[jss::result];
380  if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
381  return;
382  jrr.removeMember(jss::status);
383  if (!BEAST_EXPECT(jrr.size() == 1))
384  return;
385  auto feature = *(jrr.begin());
386  BEAST_EXPECTS(feature[jss::name] == featureName, "name");
387  BEAST_EXPECTS(
388  feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
389  "vetoed");
390 
391  jrr = env.rpc("feature", featureName, "reject")[jss::result];
392  if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
393  return;
394  jrr.removeMember(jss::status);
395  if (!BEAST_EXPECT(jrr.size() == 1))
396  return;
397  feature = *(jrr.begin());
398  BEAST_EXPECTS(feature[jss::name] == featureName, "name");
399  BEAST_EXPECTS(
400  feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
401  "vetoed");
402 
403  jrr = env.rpc("feature", featureName, "accept")[jss::result];
404  if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
405  return;
406  jrr.removeMember(jss::status);
407  if (!BEAST_EXPECT(jrr.size() == 1))
408  return;
409  feature = *(jrr.begin());
410  BEAST_EXPECTS(feature[jss::name] == featureName, "name");
411  BEAST_EXPECTS(
412  feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
413  "vetoed");
414 
415  // anything other than accept or reject is an error
416  jrr = env.rpc("feature", featureName, "maybe");
417  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
418  BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
419  }
420 
421  void
423  {
424  testcase("Obsolete");
425 
426  using namespace test::jtx;
427  Env env{*this};
428  constexpr const char* featureName = "NonFungibleTokensV1";
429 
430  auto jrr = env.rpc("feature", featureName)[jss::result];
431  if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
432  return;
433  jrr.removeMember(jss::status);
434  if (!BEAST_EXPECT(jrr.size() == 1))
435  return;
436  auto feature = *(jrr.begin());
437  BEAST_EXPECTS(feature[jss::name] == featureName, "name");
438  BEAST_EXPECTS(
439  feature[jss::vetoed].isString() &&
440  feature[jss::vetoed].asString() == "Obsolete",
441  "vetoed");
442 
443  jrr = env.rpc("feature", featureName, "reject")[jss::result];
444  if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
445  return;
446  jrr.removeMember(jss::status);
447  if (!BEAST_EXPECT(jrr.size() == 1))
448  return;
449  feature = *(jrr.begin());
450  BEAST_EXPECTS(feature[jss::name] == featureName, "name");
451  BEAST_EXPECTS(
452  feature[jss::vetoed].isString() &&
453  feature[jss::vetoed].asString() == "Obsolete",
454  "vetoed");
455 
456  jrr = env.rpc("feature", featureName, "accept")[jss::result];
457  if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
458  return;
459  jrr.removeMember(jss::status);
460  if (!BEAST_EXPECT(jrr.size() == 1))
461  return;
462  feature = *(jrr.begin());
463  BEAST_EXPECTS(feature[jss::name] == featureName, "name");
464  BEAST_EXPECTS(
465  feature[jss::vetoed].isString() &&
466  feature[jss::vetoed].asString() == "Obsolete",
467  "vetoed");
468 
469  // anything other than accept or reject is an error
470  jrr = env.rpc("feature", featureName, "maybe");
471  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
472  BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
473  }
474 
475 public:
476  void
477  run() override
478  {
479  testInternals();
481  testNoParams();
484  testNonAdmin();
485  testSomeEnabled();
487  testVeto();
488  testObsolete();
489  }
490 };
491 
492 BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
493 
494 } // namespace ripple
ripple::getMajorityAmendments
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition: View.cpp:621
ripple::AmendmentVote::up
@ up
ripple::Feature_test::testSingleFeature
void testSingleFeature()
Definition: Feature_test.cpp:154
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::Feature_test::testVeto
void testVeto()
Definition: Feature_test.cpp:371
ripple::AmendmentVote::obsolete
@ obsolete
std::pair
ripple::featureDepositPreauth
const uint256 featureDepositPreauth
ripple::VoteBehavior::DefaultNo
@ DefaultNo
ripple::Feature_test::run
void run() override
Definition: Feature_test.cpp:477
ripple::Feature_test::testFeatureLookups
void testFeatureLookups()
Definition: Feature_test.cpp:65
ripple::featureDepositAuth
const uint256 featureDepositAuth
ripple::featureMultiSignReserve
const uint256 featureMultiSignReserve
ripple::VoteBehavior::Obsolete
@ Obsolete
ripple::detail::numUpVotedAmendments
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition: Feature.cpp:334
ripple::AmendmentVote::down
@ down
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::Feature_test::testSomeEnabled
void testSomeEnabled()
Definition: Feature_test.cpp:216
ripple::Feature_test
Definition: Feature_test.cpp:27
std::map::at
T at(T... args)
ripple::fixTakerDryOfferRemoval
const uint256 fixTakerDryOfferRemoval
ripple::featureToBitsetIndex
size_t featureToBitsetIndex(uint256 const &f)
Definition: Feature.cpp:369
std::map
STL class.
ripple::Feature_test::testInternals
void testInternals()
Definition: Feature_test.cpp:30
ripple::fix1578
const uint256 fix1578
ripple::Feature_test::testWithMajorities
void testWithMajorities()
Definition: Feature_test.cpp:272
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::featureFlow
const uint256 featureFlow
ripple::featureNegativeUNL
const uint256 featureNegativeUNL
ripple::bitsetIndexToFeature
uint256 bitsetIndexToFeature(size_t i)
Definition: Feature.cpp:375
ripple::FeatureBitset
Definition: Feature.h:113
ripple::Feature_test::testNoParams
void testNoParams()
Definition: Feature_test.cpp:109
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::featureOwnerPaysFee
const uint256 featureOwnerPaysFee
ripple::Feature_test::testNonAdmin
void testNonAdmin()
Definition: Feature_test.cpp:197
ripple::getRegisteredFeature
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:342
ripple::Feature_test::testInvalidFeature
void testInvalidFeature()
Definition: Feature_test.cpp:184
std::unique_ptr
STL class.
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::Feature_test::testObsolete
void testObsolete()
Definition: Feature_test.cpp:422
ripple::featureToName
std::string featureToName(uint256 const &f)
Definition: Feature.cpp:381
ripple::VoteBehavior::DefaultYes
@ DefaultYes