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 =
313 env.rpc("json", "feature", to_string(params))[jss::result];
314 BEAST_EXPECTS(
315 result[jss::error] == "badFeature", result.toStyledString());
316 BEAST_EXPECT(
317 result[jss::error_message] == "Feature unknown or invalid.");
318 }
319
320 {
321 Json::Value params;
322 params[jss::feature] =
323 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
324 "A7";
325 // invalid param
326 params[jss::vetoed] = true;
327 auto const result =
328 env.rpc("json", "feature", to_string(params))[jss::result];
329 BEAST_EXPECTS(
330 result[jss::error] == "noPermission",
331 result[jss::error].asString());
332 BEAST_EXPECT(
333 result[jss::error_message] ==
334 "You don't have permission for this command.");
335 }
336
337 {
338 std::string const feature =
339 "C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD"
340 "37";
341 Json::Value params;
342 params[jss::feature] = feature;
343 auto const result =
344 env.rpc("json", "feature", to_string(params))[jss::result];
345 BEAST_EXPECT(result.isMember(feature));
346 auto const amendmentResult = result[feature];
347 BEAST_EXPECT(amendmentResult[jss::enabled].asBool() == false);
348 BEAST_EXPECT(amendmentResult[jss::supported].asBool() == true);
349 BEAST_EXPECT(
350 amendmentResult[jss::name].asString() ==
351 "fixMasterKeyAsRegularKey");
352 }
353 }
354
355 void
357 {
358 testcase("No Params, Some Enabled");
359
360 using namespace test::jtx;
361 Env env{
362 *this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
363
366
367 auto jrr = env.rpc("feature")[jss::result];
368 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
369 return;
370 for (auto it = jrr[jss::features].begin();
371 it != jrr[jss::features].end();
372 ++it)
373 {
374 uint256 id;
375 (void)id.parseHex(it.key().asString().c_str());
376 if (!BEAST_EXPECT((*it).isMember(jss::name)))
377 return;
378 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
379 bool expectSupported =
380 env.app().getAmendmentTable().isSupported(id);
381 bool expectVeto =
382 (votes.at((*it)[jss::name].asString()) ==
384 bool expectObsolete =
385 (votes.at((*it)[jss::name].asString()) ==
387 BEAST_EXPECTS(
388 (*it).isMember(jss::enabled) &&
389 (*it)[jss::enabled].asBool() == expectEnabled,
390 (*it)[jss::name].asString() + " enabled");
391 if (expectEnabled)
392 BEAST_EXPECTS(
393 !(*it).isMember(jss::vetoed),
394 (*it)[jss::name].asString() + " vetoed");
395 else
396 BEAST_EXPECTS(
397 (*it).isMember(jss::vetoed) &&
398 (*it)[jss::vetoed].isBool() == !expectObsolete &&
399 (!(*it)[jss::vetoed].isBool() ||
400 (*it)[jss::vetoed].asBool() == expectVeto) &&
401 ((*it)[jss::vetoed].isBool() ||
402 (*it)[jss::vetoed].asString() == "Obsolete"),
403 (*it)[jss::name].asString() + " vetoed");
404 BEAST_EXPECTS(
405 (*it).isMember(jss::supported) &&
406 (*it)[jss::supported].asBool() == expectSupported,
407 (*it)[jss::name].asString() + " supported");
408 }
409 }
410
411 void
413 {
414 testcase("With Majorities");
415
416 using namespace test::jtx;
417 Env env{*this, envconfig(validator, "")};
418
419 auto jrr = env.rpc("feature")[jss::result];
420 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
421 return;
422
423 // at this point, there are no majorities so no fields related to
424 // amendment voting
425 for (auto const& feature : jrr[jss::features])
426 {
427 if (!BEAST_EXPECT(feature.isMember(jss::name)))
428 return;
429 BEAST_EXPECTS(
430 !feature.isMember(jss::majority),
431 feature[jss::name].asString() + " majority");
432 BEAST_EXPECTS(
433 !feature.isMember(jss::count),
434 feature[jss::name].asString() + " count");
435 BEAST_EXPECTS(
436 !feature.isMember(jss::threshold),
437 feature[jss::name].asString() + " threshold");
438 BEAST_EXPECTS(
439 !feature.isMember(jss::validations),
440 feature[jss::name].asString() + " validations");
441 BEAST_EXPECTS(
442 !feature.isMember(jss::vote),
443 feature[jss::name].asString() + " vote");
444 }
445
446 auto majorities = getMajorityAmendments(*env.closed());
447 if (!BEAST_EXPECT(majorities.empty()))
448 return;
449
450 // close ledgers until the amendments show up.
451 for (auto i = 0; i <= 256; ++i)
452 {
453 env.close();
454 majorities = getMajorityAmendments(*env.closed());
455 if (!majorities.empty())
456 break;
457 }
458
459 // There should be at least 5 amendments. Don't do exact comparison
460 // to avoid maintenance as more amendments are added in the future.
461 BEAST_EXPECT(majorities.size() >= 5);
464
465 jrr = env.rpc("feature")[jss::result];
466 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
467 return;
468 for (auto const& feature : jrr[jss::features])
469 {
470 if (!BEAST_EXPECT(feature.isMember(jss::name)))
471 return;
472 bool expectVeto =
473 (votes.at(feature[jss::name].asString()) ==
475 bool expectObsolete =
476 (votes.at(feature[jss::name].asString()) ==
478 BEAST_EXPECTS(
479 (expectVeto || expectObsolete) ^
480 feature.isMember(jss::majority),
481 feature[jss::name].asString() + " majority");
482 BEAST_EXPECTS(
483 feature.isMember(jss::vetoed) &&
484 feature[jss::vetoed].isBool() == !expectObsolete &&
485 (!feature[jss::vetoed].isBool() ||
486 feature[jss::vetoed].asBool() == expectVeto) &&
487 (feature[jss::vetoed].isBool() ||
488 feature[jss::vetoed].asString() == "Obsolete"),
489 feature[jss::name].asString() + " vetoed");
490 BEAST_EXPECTS(
491 feature.isMember(jss::count),
492 feature[jss::name].asString() + " count");
493 BEAST_EXPECTS(
494 feature.isMember(jss::threshold),
495 feature[jss::name].asString() + " threshold");
496 BEAST_EXPECTS(
497 feature.isMember(jss::validations),
498 feature[jss::name].asString() + " validations");
499 BEAST_EXPECT(
500 feature[jss::count] ==
501 ((expectVeto || expectObsolete) ? 0 : 1));
502 BEAST_EXPECT(feature[jss::threshold] == 1);
503 BEAST_EXPECT(feature[jss::validations] == 1);
504 BEAST_EXPECTS(
505 expectVeto || expectObsolete || feature[jss::majority] == 2540,
506 "Majority: " + feature[jss::majority].asString());
507 }
508 }
509
510 void
512 {
513 testcase("Veto");
514
515 using namespace test::jtx;
516 Env env{*this, FeatureBitset(featureMultiSignReserve)};
517 constexpr char const* featureName = "MultiSignReserve";
518
519 auto jrr = env.rpc("feature", featureName)[jss::result];
520 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
521 return;
522 jrr.removeMember(jss::status);
523 if (!BEAST_EXPECT(jrr.size() == 1))
524 return;
525 auto feature = *(jrr.begin());
526 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
527 BEAST_EXPECTS(
528 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
529 "vetoed");
530
531 jrr = env.rpc("feature", featureName, "reject")[jss::result];
532 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
533 return;
534 jrr.removeMember(jss::status);
535 if (!BEAST_EXPECT(jrr.size() == 1))
536 return;
537 feature = *(jrr.begin());
538 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
539 BEAST_EXPECTS(
540 feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
541 "vetoed");
542
543 jrr = env.rpc("feature", featureName, "accept")[jss::result];
544 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
545 return;
546 jrr.removeMember(jss::status);
547 if (!BEAST_EXPECT(jrr.size() == 1))
548 return;
549 feature = *(jrr.begin());
550 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
551 BEAST_EXPECTS(
552 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
553 "vetoed");
554
555 // anything other than accept or reject is an error
556 jrr = env.rpc("feature", featureName, "maybe");
557 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
558 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
559 }
560
561 void
563 {
564 testcase("Obsolete");
565
566 using namespace test::jtx;
567 Env env{*this};
568 constexpr char const* featureName = "NonFungibleTokensV1";
569
570 auto jrr = env.rpc("feature", featureName)[jss::result];
571 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
572 return;
573 jrr.removeMember(jss::status);
574 if (!BEAST_EXPECT(jrr.size() == 1))
575 return;
576 auto feature = *(jrr.begin());
577 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
578 BEAST_EXPECTS(
579 feature[jss::vetoed].isString() &&
580 feature[jss::vetoed].asString() == "Obsolete",
581 "vetoed");
582
583 jrr = env.rpc("feature", featureName, "reject")[jss::result];
584 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
585 return;
586 jrr.removeMember(jss::status);
587 if (!BEAST_EXPECT(jrr.size() == 1))
588 return;
589 feature = *(jrr.begin());
590 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
591 BEAST_EXPECTS(
592 feature[jss::vetoed].isString() &&
593 feature[jss::vetoed].asString() == "Obsolete",
594 "vetoed");
595
596 jrr = env.rpc("feature", featureName, "accept")[jss::result];
597 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
598 return;
599 jrr.removeMember(jss::status);
600 if (!BEAST_EXPECT(jrr.size() == 1))
601 return;
602 feature = *(jrr.begin());
603 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
604 BEAST_EXPECTS(
605 feature[jss::vetoed].isString() &&
606 feature[jss::vetoed].asString() == "Obsolete",
607 "vetoed");
608
609 // anything other than accept or reject is an error
610 jrr = env.rpc("feature", featureName, "maybe");
611 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
612 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
613 }
614
615public:
616 void
617 run() override
618 {
621 testNoParams();
624 testNonAdmin();
627 testVeto();
628 testObsolete();
629 }
630};
631
632BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
633
634} // 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