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