rippled
Loading...
Searching...
No Matches
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 <test/jtx.h>
21
22#include <xrpld/app/misc/AmendmentTable.h>
23
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/jss.h>
26
27namespace ripple {
28
30{
31 void
33 {
34 testcase("internals");
35
36 auto const& supportedAmendments = ripple::detail::supportedAmendments();
38
39 BEAST_EXPECT(
40 supportedAmendments.size() ==
43 {
44 std::size_t up = 0, down = 0, obsolete = 0;
45 for (auto const& [name, vote] : supportedAmendments)
46 {
47 switch (vote)
48 {
50 ++up;
51 break;
53 ++down;
54 break;
56 ++obsolete;
57 break;
58 default:
59 fail("Unknown VoteBehavior", __FILE__, __LINE__);
60 }
61
62 if (vote == VoteBehavior::Obsolete)
63 {
64 BEAST_EXPECT(
65 allAmendments.contains(name) &&
67 }
68 else
69 {
70 BEAST_EXPECT(
71 allAmendments.contains(name) &&
73 }
74 }
75 BEAST_EXPECT(
78 }
79 {
80 std::size_t supported = 0, unsupported = 0, retired = 0;
81 for (auto const& [name, support] : allAmendments)
82 {
83 switch (support)
84 {
86 ++supported;
87 BEAST_EXPECT(supportedAmendments.contains(name));
88 break;
90 ++unsupported;
91 break;
93 ++retired;
94 break;
95 default:
96 fail("Unknown AmendmentSupport", __FILE__, __LINE__);
97 }
98 }
99
100 BEAST_EXPECT(supported + retired == supportedAmendments.size());
101 BEAST_EXPECT(
102 allAmendments.size() - unsupported ==
103 supportedAmendments.size());
104 }
105 }
106
107 void
109 {
110 testcase("featureToName");
111
112 // Test all the supported features. In a perfect world, this would test
113 // FeatureCollections::featureNames, but that's private. Leave it that
114 // way.
115 auto const supported = ripple::detail::supportedAmendments();
116
117 for (auto const& [feature, vote] : supported)
118 {
119 (void)vote;
120 auto const registered = getRegisteredFeature(feature);
121 if (BEAST_EXPECT(registered))
122 {
123 BEAST_EXPECT(featureToName(*registered) == feature);
124 BEAST_EXPECT(
126 *registered);
127 }
128 }
129
130 // Test an arbitrary unknown feature
131 uint256 zero{0};
132 BEAST_EXPECT(featureToName(zero) == to_string(zero));
133 BEAST_EXPECT(
134 featureToName(zero) ==
135 "0000000000000000000000000000000000000000000000000000000000000000");
136
137 // Test looking up an unknown feature
138 BEAST_EXPECT(!getRegisteredFeature("unknown"));
139
140 // Test a random sampling of the variables. If any of these get retired
141 // or removed, swap out for any other feature.
142 BEAST_EXPECT(
143 featureToName(fixTrustLinesToSelf) == "fixTrustLinesToSelf");
144 BEAST_EXPECT(featureToName(featureFlow) == "Flow");
145 BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
146 BEAST_EXPECT(
147 featureToName(fixTakerDryOfferRemoval) ==
148 "fixTakerDryOfferRemoval");
149 }
150
151 void
153 {
154 testcase("No Params, None Enabled");
155
156 using namespace test::jtx;
157 Env env{*this};
158
161
162 auto jrr = env.rpc("feature")[jss::result];
163 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
164 return;
165 for (auto const& feature : jrr[jss::features])
166 {
167 if (!BEAST_EXPECT(feature.isMember(jss::name)))
168 return;
169 // default config - so all should be disabled, and
170 // supported. Some may be vetoed.
171 bool expectVeto =
172 (votes.at(feature[jss::name].asString()) ==
174 bool expectObsolete =
175 (votes.at(feature[jss::name].asString()) ==
177 BEAST_EXPECTS(
178 feature.isMember(jss::enabled) &&
179 !feature[jss::enabled].asBool(),
180 feature[jss::name].asString() + " enabled");
181 BEAST_EXPECTS(
182 feature.isMember(jss::vetoed) &&
183 feature[jss::vetoed].isBool() == !expectObsolete &&
184 (!feature[jss::vetoed].isBool() ||
185 feature[jss::vetoed].asBool() == expectVeto) &&
186 (feature[jss::vetoed].isBool() ||
187 feature[jss::vetoed].asString() == "Obsolete"),
188 feature[jss::name].asString() + " vetoed");
189 BEAST_EXPECTS(
190 feature.isMember(jss::supported) &&
191 feature[jss::supported].asBool(),
192 feature[jss::name].asString() + " supported");
193 }
194 }
195
196 void
198 {
199 testcase("Feature Param");
200
201 using namespace test::jtx;
202 Env env{*this};
203
204 auto jrr = env.rpc("feature", "MultiSignReserve")[jss::result];
205 BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
206 jrr.removeMember(jss::status);
207 BEAST_EXPECT(jrr.size() == 1);
208 BEAST_EXPECT(
209 jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
210 "1FC07EFE41D"));
211 auto feature = *(jrr.begin());
212
213 BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
214 BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
215 BEAST_EXPECTS(
216 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
217 "vetoed");
218 BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
219
220 // feature names are case-sensitive - expect error here
221 jrr = env.rpc("feature", "multiSignReserve")[jss::result];
222 BEAST_EXPECT(jrr[jss::error] == "badFeature");
223 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
224 }
225
226 void
228 {
229 testcase("Invalid Feature");
230
231 using namespace test::jtx;
232 Env env{*this};
233
234 auto testInvalidParam = [&](auto const& param) {
235 Json::Value params;
236 params[jss::feature] = param;
237 auto jrr =
238 env.rpc("json", "feature", to_string(params))[jss::result];
239 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
240 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
241 };
242
243 testInvalidParam(1);
244 testInvalidParam(1.1);
245 testInvalidParam(true);
246 testInvalidParam(Json::Value(Json::nullValue));
247 testInvalidParam(Json::Value(Json::objectValue));
248 testInvalidParam(Json::Value(Json::arrayValue));
249
250 {
251 auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
252 BEAST_EXPECT(jrr[jss::error] == "badFeature");
253 BEAST_EXPECT(
254 jrr[jss::error_message] == "Feature unknown or invalid.");
255 }
256 }
257
258 void
260 {
261 testcase("Feature Without Admin");
262
263 using namespace test::jtx;
264 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
265 (*cfg)["port_rpc"].set("admin", "");
266 (*cfg)["port_ws"].set("admin", "");
267 return cfg;
268 })};
269
270 {
271 auto result = env.rpc("feature")[jss::result];
272 BEAST_EXPECT(result.isMember(jss::features));
273 // There should be at least 50 amendments. Don't do exact
274 // comparison to avoid maintenance as more amendments are added in
275 // the future.
276 BEAST_EXPECT(result[jss::features].size() >= 50);
277 for (auto it = result[jss::features].begin();
278 it != result[jss::features].end();
279 ++it)
280 {
281 uint256 id;
282 (void)id.parseHex(it.key().asString().c_str());
283 if (!BEAST_EXPECT((*it).isMember(jss::name)))
284 return;
285 bool expectEnabled =
286 env.app().getAmendmentTable().isEnabled(id);
287 bool expectSupported =
288 env.app().getAmendmentTable().isSupported(id);
289 BEAST_EXPECTS(
290 (*it).isMember(jss::enabled) &&
291 (*it)[jss::enabled].asBool() == expectEnabled,
292 (*it)[jss::name].asString() + " enabled");
293 BEAST_EXPECTS(
294 (*it).isMember(jss::supported) &&
295 (*it)[jss::supported].asBool() == expectSupported,
296 (*it)[jss::name].asString() + " supported");
297 BEAST_EXPECT(!(*it).isMember(jss::vetoed));
298 BEAST_EXPECT(!(*it).isMember(jss::majority));
299 BEAST_EXPECT(!(*it).isMember(jss::count));
300 BEAST_EXPECT(!(*it).isMember(jss::validations));
301 BEAST_EXPECT(!(*it).isMember(jss::threshold));
302 }
303 }
304
305 {
306 Json::Value params;
307 // invalid feature
308 params[jss::feature] =
309 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
310 "EF";
311 auto const result =
312 env.rpc("json", "feature", to_string(params))[jss::result];
313 BEAST_EXPECTS(
314 result[jss::error] == "badFeature", result.toStyledString());
315 BEAST_EXPECT(
316 result[jss::error_message] == "Feature unknown or invalid.");
317 }
318
319 {
320 Json::Value params;
321 params[jss::feature] =
322 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
323 "A7";
324 // invalid param
325 params[jss::vetoed] = true;
326 auto const result =
327 env.rpc("json", "feature", to_string(params))[jss::result];
328 BEAST_EXPECTS(
329 result[jss::error] == "noPermission",
330 result[jss::error].asString());
331 BEAST_EXPECT(
332 result[jss::error_message] ==
333 "You don't have permission for this command.");
334 }
335
336 {
337 std::string const feature =
338 "C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD"
339 "37";
340 Json::Value params;
341 params[jss::feature] = feature;
342 auto const result =
343 env.rpc("json", "feature", to_string(params))[jss::result];
344 BEAST_EXPECT(result.isMember(feature));
345 auto const amendmentResult = result[feature];
346 BEAST_EXPECT(amendmentResult[jss::enabled].asBool() == false);
347 BEAST_EXPECT(amendmentResult[jss::supported].asBool() == true);
348 BEAST_EXPECT(
349 amendmentResult[jss::name].asString() ==
350 "fixMasterKeyAsRegularKey");
351 }
352 }
353
354 void
356 {
357 testcase("No Params, Some Enabled");
358
359 using namespace test::jtx;
360 Env env{
361 *this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
362
365
366 auto jrr = env.rpc("feature")[jss::result];
367 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
368 return;
369 for (auto it = jrr[jss::features].begin();
370 it != jrr[jss::features].end();
371 ++it)
372 {
373 uint256 id;
374 (void)id.parseHex(it.key().asString().c_str());
375 if (!BEAST_EXPECT((*it).isMember(jss::name)))
376 return;
377 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
378 bool expectSupported =
379 env.app().getAmendmentTable().isSupported(id);
380 bool expectVeto =
381 (votes.at((*it)[jss::name].asString()) ==
383 bool expectObsolete =
384 (votes.at((*it)[jss::name].asString()) ==
386 BEAST_EXPECTS(
387 (*it).isMember(jss::enabled) &&
388 (*it)[jss::enabled].asBool() == expectEnabled,
389 (*it)[jss::name].asString() + " enabled");
390 if (expectEnabled)
391 BEAST_EXPECTS(
392 !(*it).isMember(jss::vetoed),
393 (*it)[jss::name].asString() + " vetoed");
394 else
395 BEAST_EXPECTS(
396 (*it).isMember(jss::vetoed) &&
397 (*it)[jss::vetoed].isBool() == !expectObsolete &&
398 (!(*it)[jss::vetoed].isBool() ||
399 (*it)[jss::vetoed].asBool() == expectVeto) &&
400 ((*it)[jss::vetoed].isBool() ||
401 (*it)[jss::vetoed].asString() == "Obsolete"),
402 (*it)[jss::name].asString() + " vetoed");
403 BEAST_EXPECTS(
404 (*it).isMember(jss::supported) &&
405 (*it)[jss::supported].asBool() == expectSupported,
406 (*it)[jss::name].asString() + " supported");
407 }
408 }
409
410 void
412 {
413 testcase("With Majorities");
414
415 using namespace test::jtx;
416 Env env{*this, envconfig(validator, "")};
417
418 auto jrr = env.rpc("feature")[jss::result];
419 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
420 return;
421
422 // at this point, there are no majorities so no fields related to
423 // amendment voting
424 for (auto const& feature : jrr[jss::features])
425 {
426 if (!BEAST_EXPECT(feature.isMember(jss::name)))
427 return;
428 BEAST_EXPECTS(
429 !feature.isMember(jss::majority),
430 feature[jss::name].asString() + " majority");
431 BEAST_EXPECTS(
432 !feature.isMember(jss::count),
433 feature[jss::name].asString() + " count");
434 BEAST_EXPECTS(
435 !feature.isMember(jss::threshold),
436 feature[jss::name].asString() + " threshold");
437 BEAST_EXPECTS(
438 !feature.isMember(jss::validations),
439 feature[jss::name].asString() + " validations");
440 BEAST_EXPECTS(
441 !feature.isMember(jss::vote),
442 feature[jss::name].asString() + " vote");
443 }
444
445 auto majorities = getMajorityAmendments(*env.closed());
446 if (!BEAST_EXPECT(majorities.empty()))
447 return;
448
449 // close ledgers until the amendments show up.
450 for (auto i = 0; i <= 256; ++i)
451 {
452 env.close();
453 majorities = getMajorityAmendments(*env.closed());
454 if (!majorities.empty())
455 break;
456 }
457
458 // There should be at least 5 amendments. Don't do exact comparison
459 // to avoid maintenance as more amendments are added in the future.
460 BEAST_EXPECT(majorities.size() >= 5);
463
464 jrr = env.rpc("feature")[jss::result];
465 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
466 return;
467 for (auto const& feature : jrr[jss::features])
468 {
469 if (!BEAST_EXPECT(feature.isMember(jss::name)))
470 return;
471 bool expectVeto =
472 (votes.at(feature[jss::name].asString()) ==
474 bool expectObsolete =
475 (votes.at(feature[jss::name].asString()) ==
477 BEAST_EXPECTS(
478 (expectVeto || expectObsolete) ^
479 feature.isMember(jss::majority),
480 feature[jss::name].asString() + " majority");
481 BEAST_EXPECTS(
482 feature.isMember(jss::vetoed) &&
483 feature[jss::vetoed].isBool() == !expectObsolete &&
484 (!feature[jss::vetoed].isBool() ||
485 feature[jss::vetoed].asBool() == expectVeto) &&
486 (feature[jss::vetoed].isBool() ||
487 feature[jss::vetoed].asString() == "Obsolete"),
488 feature[jss::name].asString() + " vetoed");
489 BEAST_EXPECTS(
490 feature.isMember(jss::count),
491 feature[jss::name].asString() + " count");
492 BEAST_EXPECTS(
493 feature.isMember(jss::threshold),
494 feature[jss::name].asString() + " threshold");
495 BEAST_EXPECTS(
496 feature.isMember(jss::validations),
497 feature[jss::name].asString() + " validations");
498 BEAST_EXPECT(
499 feature[jss::count] ==
500 ((expectVeto || expectObsolete) ? 0 : 1));
501 BEAST_EXPECT(feature[jss::threshold] == 1);
502 BEAST_EXPECT(feature[jss::validations] == 1);
503 BEAST_EXPECTS(
504 expectVeto || expectObsolete || feature[jss::majority] == 2540,
505 "Majority: " + feature[jss::majority].asString());
506 }
507 }
508
509 void
511 {
512 testcase("Veto");
513
514 using namespace test::jtx;
515 Env env{*this, FeatureBitset(featureMultiSignReserve)};
516 constexpr char const* featureName = "MultiSignReserve";
517
518 auto jrr = env.rpc("feature", featureName)[jss::result];
519 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
520 return;
521 jrr.removeMember(jss::status);
522 if (!BEAST_EXPECT(jrr.size() == 1))
523 return;
524 auto feature = *(jrr.begin());
525 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
526 BEAST_EXPECTS(
527 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
528 "vetoed");
529
530 jrr = env.rpc("feature", featureName, "reject")[jss::result];
531 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
532 return;
533 jrr.removeMember(jss::status);
534 if (!BEAST_EXPECT(jrr.size() == 1))
535 return;
536 feature = *(jrr.begin());
537 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
538 BEAST_EXPECTS(
539 feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
540 "vetoed");
541
542 jrr = env.rpc("feature", featureName, "accept")[jss::result];
543 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
544 return;
545 jrr.removeMember(jss::status);
546 if (!BEAST_EXPECT(jrr.size() == 1))
547 return;
548 feature = *(jrr.begin());
549 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
550 BEAST_EXPECTS(
551 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
552 "vetoed");
553
554 // anything other than accept or reject is an error
555 jrr = env.rpc("feature", featureName, "maybe");
556 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
557 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
558 }
559
560 void
562 {
563 testcase("Obsolete");
564
565 using namespace test::jtx;
566 Env env{*this};
567 constexpr char const* featureName = "NonFungibleTokensV1";
568
569 auto jrr = env.rpc("feature", featureName)[jss::result];
570 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
571 return;
572 jrr.removeMember(jss::status);
573 if (!BEAST_EXPECT(jrr.size() == 1))
574 return;
575 auto feature = *(jrr.begin());
576 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
577 BEAST_EXPECTS(
578 feature[jss::vetoed].isString() &&
579 feature[jss::vetoed].asString() == "Obsolete",
580 "vetoed");
581
582 jrr = env.rpc("feature", featureName, "reject")[jss::result];
583 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
584 return;
585 jrr.removeMember(jss::status);
586 if (!BEAST_EXPECT(jrr.size() == 1))
587 return;
588 feature = *(jrr.begin());
589 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
590 BEAST_EXPECTS(
591 feature[jss::vetoed].isString() &&
592 feature[jss::vetoed].asString() == "Obsolete",
593 "vetoed");
594
595 jrr = env.rpc("feature", featureName, "accept")[jss::result];
596 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
597 return;
598 jrr.removeMember(jss::status);
599 if (!BEAST_EXPECT(jrr.size() == 1))
600 return;
601 feature = *(jrr.begin());
602 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
603 BEAST_EXPECTS(
604 feature[jss::vetoed].isString() &&
605 feature[jss::vetoed].asString() == "Obsolete",
606 "vetoed");
607
608 // anything other than accept or reject is an error
609 jrr = env.rpc("feature", featureName, "maybe");
610 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
611 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
612 }
613
614public:
615 void
616 run() override
617 {
620 testNoParams();
623 testNonAdmin();
626 testVeto();
627 testObsolete();
628 }
629};
630
631BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
632
633} // namespace ripple
T at(T... args)
Represents a JSON value.
Definition json_value.h:149
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
void run() override
Runs the suite.
@ nullValue
'null' value
Definition json_value.h:38
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition Feature.cpp:374
std::size_t numDownVotedAmendments()
Amendments that this server won't vote for by default.
Definition Feature.cpp:367
std::map< std::string, VoteBehavior > const & supportedAmendments()
Amendments that this server supports and the default voting behavior.
Definition Feature.cpp:360
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
uint256 bitsetIndexToFeature(size_t i)
Definition Feature.cpp:415
size_t featureToBitsetIndex(uint256 const &f)
Definition Feature.cpp:409
std::map< std::string, AmendmentSupport > const & allAmendments()
All amendments libxrpl knows about.
Definition Feature.cpp:351
std::string featureToName(uint256 const &f)
Definition Feature.cpp:421
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition Feature.cpp:382
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:938
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630