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(featureToName(fix1578) == "fix1578");
147 BEAST_EXPECT(
148 featureToName(fixTakerDryOfferRemoval) ==
149 "fixTakerDryOfferRemoval");
150 }
151
152 void
154 {
155 testcase("No Params, None Enabled");
156
157 using namespace test::jtx;
158 Env env{*this};
159
162
163 auto jrr = env.rpc("feature")[jss::result];
164 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
165 return;
166 for (auto const& feature : jrr[jss::features])
167 {
168 if (!BEAST_EXPECT(feature.isMember(jss::name)))
169 return;
170 // default config - so all should be disabled, and
171 // supported. Some may be vetoed.
172 bool expectVeto =
173 (votes.at(feature[jss::name].asString()) ==
175 bool expectObsolete =
176 (votes.at(feature[jss::name].asString()) ==
178 BEAST_EXPECTS(
179 feature.isMember(jss::enabled) &&
180 !feature[jss::enabled].asBool(),
181 feature[jss::name].asString() + " enabled");
182 BEAST_EXPECTS(
183 feature.isMember(jss::vetoed) &&
184 feature[jss::vetoed].isBool() == !expectObsolete &&
185 (!feature[jss::vetoed].isBool() ||
186 feature[jss::vetoed].asBool() == expectVeto) &&
187 (feature[jss::vetoed].isBool() ||
188 feature[jss::vetoed].asString() == "Obsolete"),
189 feature[jss::name].asString() + " vetoed");
190 BEAST_EXPECTS(
191 feature.isMember(jss::supported) &&
192 feature[jss::supported].asBool(),
193 feature[jss::name].asString() + " supported");
194 }
195 }
196
197 void
199 {
200 testcase("Feature Param");
201
202 using namespace test::jtx;
203 Env env{*this};
204
205 auto jrr = env.rpc("feature", "MultiSignReserve")[jss::result];
206 BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
207 jrr.removeMember(jss::status);
208 BEAST_EXPECT(jrr.size() == 1);
209 BEAST_EXPECT(
210 jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
211 "1FC07EFE41D"));
212 auto feature = *(jrr.begin());
213
214 BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
215 BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
216 BEAST_EXPECTS(
217 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
218 "vetoed");
219 BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
220
221 // feature names are case-sensitive - expect error here
222 jrr = env.rpc("feature", "multiSignReserve")[jss::result];
223 BEAST_EXPECT(jrr[jss::error] == "badFeature");
224 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
225 }
226
227 void
229 {
230 testcase("Invalid Feature");
231
232 using namespace test::jtx;
233 Env env{*this};
234
235 auto testInvalidParam = [&](auto const& param) {
236 Json::Value params;
237 params[jss::feature] = param;
238 auto jrr =
239 env.rpc("json", "feature", to_string(params))[jss::result];
240 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
241 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
242 };
243
244 testInvalidParam(1);
245 testInvalidParam(1.1);
246 testInvalidParam(true);
247 testInvalidParam(Json::Value(Json::nullValue));
248 testInvalidParam(Json::Value(Json::objectValue));
249 testInvalidParam(Json::Value(Json::arrayValue));
250
251 {
252 auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
253 BEAST_EXPECT(jrr[jss::error] == "badFeature");
254 BEAST_EXPECT(
255 jrr[jss::error_message] == "Feature unknown or invalid.");
256 }
257 }
258
259 void
261 {
262 testcase("Feature Without Admin");
263
264 using namespace test::jtx;
265 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
266 (*cfg)["port_rpc"].set("admin", "");
267 (*cfg)["port_ws"].set("admin", "");
268 return cfg;
269 })};
270
271 {
272 auto result = env.rpc("feature")[jss::result];
273 BEAST_EXPECT(result.isMember(jss::features));
274 // There should be at least 50 amendments. Don't do exact
275 // comparison to avoid maintenance as more amendments are added in
276 // the future.
277 BEAST_EXPECT(result[jss::features].size() >= 50);
278 for (auto it = result[jss::features].begin();
279 it != result[jss::features].end();
280 ++it)
281 {
282 uint256 id;
283 (void)id.parseHex(it.key().asString().c_str());
284 if (!BEAST_EXPECT((*it).isMember(jss::name)))
285 return;
286 bool expectEnabled =
287 env.app().getAmendmentTable().isEnabled(id);
288 bool expectSupported =
289 env.app().getAmendmentTable().isSupported(id);
290 BEAST_EXPECTS(
291 (*it).isMember(jss::enabled) &&
292 (*it)[jss::enabled].asBool() == expectEnabled,
293 (*it)[jss::name].asString() + " enabled");
294 BEAST_EXPECTS(
295 (*it).isMember(jss::supported) &&
296 (*it)[jss::supported].asBool() == expectSupported,
297 (*it)[jss::name].asString() + " supported");
298 BEAST_EXPECT(!(*it).isMember(jss::vetoed));
299 BEAST_EXPECT(!(*it).isMember(jss::majority));
300 BEAST_EXPECT(!(*it).isMember(jss::count));
301 BEAST_EXPECT(!(*it).isMember(jss::validations));
302 BEAST_EXPECT(!(*it).isMember(jss::threshold));
303 }
304 }
305
306 {
307 Json::Value params;
308 // invalid feature
309 params[jss::feature] =
310 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
311 "EF";
312 auto const result = env.rpc(
313 "json",
314 "feature",
315 boost::lexical_cast<std::string>(params))[jss::result];
316 BEAST_EXPECTS(
317 result[jss::error] == "badFeature", result.toStyledString());
318 BEAST_EXPECT(
319 result[jss::error_message] == "Feature unknown or invalid.");
320 }
321
322 {
323 Json::Value params;
324 params[jss::feature] =
325 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
326 "A7";
327 // invalid param
328 params[jss::vetoed] = true;
329 auto const result = env.rpc(
330 "json",
331 "feature",
332 boost::lexical_cast<std::string>(params))[jss::result];
333 BEAST_EXPECTS(
334 result[jss::error] == "noPermission",
335 result[jss::error].asString());
336 BEAST_EXPECT(
337 result[jss::error_message] ==
338 "You don't have permission for this command.");
339 }
340
341 {
342 std::string const feature =
343 "C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD"
344 "37";
345 Json::Value params;
346 params[jss::feature] = feature;
347 auto const result = env.rpc(
348 "json",
349 "feature",
350 boost::lexical_cast<std::string>(params))[jss::result];
351 BEAST_EXPECT(result.isMember(feature));
352 auto const amendmentResult = result[feature];
353 BEAST_EXPECT(amendmentResult[jss::enabled].asBool() == false);
354 BEAST_EXPECT(amendmentResult[jss::supported].asBool() == true);
355 BEAST_EXPECT(
356 amendmentResult[jss::name].asString() ==
357 "fixMasterKeyAsRegularKey");
358 }
359 }
360
361 void
363 {
364 testcase("No Params, Some Enabled");
365
366 using namespace test::jtx;
367 Env env{
368 *this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
369
372
373 auto jrr = env.rpc("feature")[jss::result];
374 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
375 return;
376 for (auto it = jrr[jss::features].begin();
377 it != jrr[jss::features].end();
378 ++it)
379 {
380 uint256 id;
381 (void)id.parseHex(it.key().asString().c_str());
382 if (!BEAST_EXPECT((*it).isMember(jss::name)))
383 return;
384 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
385 bool expectSupported =
386 env.app().getAmendmentTable().isSupported(id);
387 bool expectVeto =
388 (votes.at((*it)[jss::name].asString()) ==
390 bool expectObsolete =
391 (votes.at((*it)[jss::name].asString()) ==
393 BEAST_EXPECTS(
394 (*it).isMember(jss::enabled) &&
395 (*it)[jss::enabled].asBool() == expectEnabled,
396 (*it)[jss::name].asString() + " enabled");
397 if (expectEnabled)
398 BEAST_EXPECTS(
399 !(*it).isMember(jss::vetoed),
400 (*it)[jss::name].asString() + " vetoed");
401 else
402 BEAST_EXPECTS(
403 (*it).isMember(jss::vetoed) &&
404 (*it)[jss::vetoed].isBool() == !expectObsolete &&
405 (!(*it)[jss::vetoed].isBool() ||
406 (*it)[jss::vetoed].asBool() == expectVeto) &&
407 ((*it)[jss::vetoed].isBool() ||
408 (*it)[jss::vetoed].asString() == "Obsolete"),
409 (*it)[jss::name].asString() + " vetoed");
410 BEAST_EXPECTS(
411 (*it).isMember(jss::supported) &&
412 (*it)[jss::supported].asBool() == expectSupported,
413 (*it)[jss::name].asString() + " supported");
414 }
415 }
416
417 void
419 {
420 testcase("With Majorities");
421
422 using namespace test::jtx;
423 Env env{*this, envconfig(validator, "")};
424
425 auto jrr = env.rpc("feature")[jss::result];
426 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
427 return;
428
429 // at this point, there are no majorities so no fields related to
430 // amendment voting
431 for (auto const& feature : jrr[jss::features])
432 {
433 if (!BEAST_EXPECT(feature.isMember(jss::name)))
434 return;
435 BEAST_EXPECTS(
436 !feature.isMember(jss::majority),
437 feature[jss::name].asString() + " majority");
438 BEAST_EXPECTS(
439 !feature.isMember(jss::count),
440 feature[jss::name].asString() + " count");
441 BEAST_EXPECTS(
442 !feature.isMember(jss::threshold),
443 feature[jss::name].asString() + " threshold");
444 BEAST_EXPECTS(
445 !feature.isMember(jss::validations),
446 feature[jss::name].asString() + " validations");
447 BEAST_EXPECTS(
448 !feature.isMember(jss::vote),
449 feature[jss::name].asString() + " vote");
450 }
451
452 auto majorities = getMajorityAmendments(*env.closed());
453 if (!BEAST_EXPECT(majorities.empty()))
454 return;
455
456 // close ledgers until the amendments show up.
457 for (auto i = 0; i <= 256; ++i)
458 {
459 env.close();
460 majorities = getMajorityAmendments(*env.closed());
461 if (!majorities.empty())
462 break;
463 }
464
465 // There should be at least 5 amendments. Don't do exact comparison
466 // to avoid maintenance as more amendments are added in the future.
467 BEAST_EXPECT(majorities.size() >= 5);
470
471 jrr = env.rpc("feature")[jss::result];
472 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
473 return;
474 for (auto const& feature : jrr[jss::features])
475 {
476 if (!BEAST_EXPECT(feature.isMember(jss::name)))
477 return;
478 bool expectVeto =
479 (votes.at(feature[jss::name].asString()) ==
481 bool expectObsolete =
482 (votes.at(feature[jss::name].asString()) ==
484 BEAST_EXPECTS(
485 (expectVeto || expectObsolete) ^
486 feature.isMember(jss::majority),
487 feature[jss::name].asString() + " majority");
488 BEAST_EXPECTS(
489 feature.isMember(jss::vetoed) &&
490 feature[jss::vetoed].isBool() == !expectObsolete &&
491 (!feature[jss::vetoed].isBool() ||
492 feature[jss::vetoed].asBool() == expectVeto) &&
493 (feature[jss::vetoed].isBool() ||
494 feature[jss::vetoed].asString() == "Obsolete"),
495 feature[jss::name].asString() + " vetoed");
496 BEAST_EXPECTS(
497 feature.isMember(jss::count),
498 feature[jss::name].asString() + " count");
499 BEAST_EXPECTS(
500 feature.isMember(jss::threshold),
501 feature[jss::name].asString() + " threshold");
502 BEAST_EXPECTS(
503 feature.isMember(jss::validations),
504 feature[jss::name].asString() + " validations");
505 BEAST_EXPECT(
506 feature[jss::count] ==
507 ((expectVeto || expectObsolete) ? 0 : 1));
508 BEAST_EXPECT(feature[jss::threshold] == 1);
509 BEAST_EXPECT(feature[jss::validations] == 1);
510 BEAST_EXPECTS(
511 expectVeto || expectObsolete || feature[jss::majority] == 2540,
512 "Majority: " + feature[jss::majority].asString());
513 }
514 }
515
516 void
518 {
519 testcase("Veto");
520
521 using namespace test::jtx;
522 Env env{*this, FeatureBitset(featureMultiSignReserve)};
523 constexpr char const* featureName = "MultiSignReserve";
524
525 auto jrr = env.rpc("feature", featureName)[jss::result];
526 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
527 return;
528 jrr.removeMember(jss::status);
529 if (!BEAST_EXPECT(jrr.size() == 1))
530 return;
531 auto feature = *(jrr.begin());
532 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
533 BEAST_EXPECTS(
534 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
535 "vetoed");
536
537 jrr = env.rpc("feature", featureName, "reject")[jss::result];
538 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
539 return;
540 jrr.removeMember(jss::status);
541 if (!BEAST_EXPECT(jrr.size() == 1))
542 return;
543 feature = *(jrr.begin());
544 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
545 BEAST_EXPECTS(
546 feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
547 "vetoed");
548
549 jrr = env.rpc("feature", featureName, "accept")[jss::result];
550 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
551 return;
552 jrr.removeMember(jss::status);
553 if (!BEAST_EXPECT(jrr.size() == 1))
554 return;
555 feature = *(jrr.begin());
556 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
557 BEAST_EXPECTS(
558 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
559 "vetoed");
560
561 // anything other than accept or reject is an error
562 jrr = env.rpc("feature", featureName, "maybe");
563 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
564 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
565 }
566
567 void
569 {
570 testcase("Obsolete");
571
572 using namespace test::jtx;
573 Env env{*this};
574 constexpr char const* featureName = "NonFungibleTokensV1";
575
576 auto jrr = env.rpc("feature", featureName)[jss::result];
577 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
578 return;
579 jrr.removeMember(jss::status);
580 if (!BEAST_EXPECT(jrr.size() == 1))
581 return;
582 auto feature = *(jrr.begin());
583 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
584 BEAST_EXPECTS(
585 feature[jss::vetoed].isString() &&
586 feature[jss::vetoed].asString() == "Obsolete",
587 "vetoed");
588
589 jrr = env.rpc("feature", featureName, "reject")[jss::result];
590 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
591 return;
592 jrr.removeMember(jss::status);
593 if (!BEAST_EXPECT(jrr.size() == 1))
594 return;
595 feature = *(jrr.begin());
596 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
597 BEAST_EXPECTS(
598 feature[jss::vetoed].isString() &&
599 feature[jss::vetoed].asString() == "Obsolete",
600 "vetoed");
601
602 jrr = env.rpc("feature", featureName, "accept")[jss::result];
603 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
604 return;
605 jrr.removeMember(jss::status);
606 if (!BEAST_EXPECT(jrr.size() == 1))
607 return;
608 feature = *(jrr.begin());
609 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
610 BEAST_EXPECTS(
611 feature[jss::vetoed].isString() &&
612 feature[jss::vetoed].asString() == "Obsolete",
613 "vetoed");
614
615 // anything other than accept or reject is an error
616 jrr = env.rpc("feature", featureName, "maybe");
617 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
618 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
619 }
620
621public:
622 void
623 run() override
624 {
627 testNoParams();
630 testNonAdmin();
633 testVeto();
634 testObsolete();
635 }
636};
637
638BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
639
640} // namespace ripple
T at(T... args)
Represents a JSON value.
Definition: json_value.h:150
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:39
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
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:26
uint256 bitsetIndexToFeature(size_t i)
Definition: Feature.cpp:423
size_t featureToBitsetIndex(uint256 const &f)
Definition: Feature.cpp:417
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:429
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:382
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition: View.cpp:937
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630