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(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
148 BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
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 void
339 {
340 testcase("No Params, Some Enabled");
341
342 using namespace test::jtx;
343 Env env{
344 *this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
345
348
349 auto jrr = env.rpc("feature")[jss::result];
350 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
351 return;
352 for (auto it = jrr[jss::features].begin();
353 it != jrr[jss::features].end();
354 ++it)
355 {
356 uint256 id;
357 (void)id.parseHex(it.key().asString().c_str());
358 if (!BEAST_EXPECT((*it).isMember(jss::name)))
359 return;
360 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
361 bool expectSupported =
362 env.app().getAmendmentTable().isSupported(id);
363 bool expectVeto =
364 (votes.at((*it)[jss::name].asString()) ==
366 bool expectObsolete =
367 (votes.at((*it)[jss::name].asString()) ==
369 BEAST_EXPECTS(
370 (*it).isMember(jss::enabled) &&
371 (*it)[jss::enabled].asBool() == expectEnabled,
372 (*it)[jss::name].asString() + " enabled");
373 if (expectEnabled)
374 BEAST_EXPECTS(
375 !(*it).isMember(jss::vetoed),
376 (*it)[jss::name].asString() + " vetoed");
377 else
378 BEAST_EXPECTS(
379 (*it).isMember(jss::vetoed) &&
380 (*it)[jss::vetoed].isBool() == !expectObsolete &&
381 (!(*it)[jss::vetoed].isBool() ||
382 (*it)[jss::vetoed].asBool() == expectVeto) &&
383 ((*it)[jss::vetoed].isBool() ||
384 (*it)[jss::vetoed].asString() == "Obsolete"),
385 (*it)[jss::name].asString() + " vetoed");
386 BEAST_EXPECTS(
387 (*it).isMember(jss::supported) &&
388 (*it)[jss::supported].asBool() == expectSupported,
389 (*it)[jss::name].asString() + " supported");
390 }
391 }
392
393 void
395 {
396 testcase("With Majorities");
397
398 using namespace test::jtx;
399 Env env{*this, envconfig(validator, "")};
400
401 auto jrr = env.rpc("feature")[jss::result];
402 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
403 return;
404
405 // at this point, there are no majorities so no fields related to
406 // amendment voting
407 for (auto const& feature : jrr[jss::features])
408 {
409 if (!BEAST_EXPECT(feature.isMember(jss::name)))
410 return;
411 BEAST_EXPECTS(
412 !feature.isMember(jss::majority),
413 feature[jss::name].asString() + " majority");
414 BEAST_EXPECTS(
415 !feature.isMember(jss::count),
416 feature[jss::name].asString() + " count");
417 BEAST_EXPECTS(
418 !feature.isMember(jss::threshold),
419 feature[jss::name].asString() + " threshold");
420 BEAST_EXPECTS(
421 !feature.isMember(jss::validations),
422 feature[jss::name].asString() + " validations");
423 BEAST_EXPECTS(
424 !feature.isMember(jss::vote),
425 feature[jss::name].asString() + " vote");
426 }
427
428 auto majorities = getMajorityAmendments(*env.closed());
429 if (!BEAST_EXPECT(majorities.empty()))
430 return;
431
432 // close ledgers until the amendments show up.
433 for (auto i = 0; i <= 256; ++i)
434 {
435 env.close();
436 majorities = getMajorityAmendments(*env.closed());
437 if (!majorities.empty())
438 break;
439 }
440
441 // There should be at least 5 amendments. Don't do exact comparison
442 // to avoid maintenance as more amendments are added in the future.
443 BEAST_EXPECT(majorities.size() >= 5);
446
447 jrr = env.rpc("feature")[jss::result];
448 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
449 return;
450 for (auto const& feature : jrr[jss::features])
451 {
452 if (!BEAST_EXPECT(feature.isMember(jss::name)))
453 return;
454 bool expectVeto =
455 (votes.at(feature[jss::name].asString()) ==
457 bool expectObsolete =
458 (votes.at(feature[jss::name].asString()) ==
460 BEAST_EXPECTS(
461 (expectVeto || expectObsolete) ^
462 feature.isMember(jss::majority),
463 feature[jss::name].asString() + " majority");
464 BEAST_EXPECTS(
465 feature.isMember(jss::vetoed) &&
466 feature[jss::vetoed].isBool() == !expectObsolete &&
467 (!feature[jss::vetoed].isBool() ||
468 feature[jss::vetoed].asBool() == expectVeto) &&
469 (feature[jss::vetoed].isBool() ||
470 feature[jss::vetoed].asString() == "Obsolete"),
471 feature[jss::name].asString() + " vetoed");
472 BEAST_EXPECTS(
473 feature.isMember(jss::count),
474 feature[jss::name].asString() + " count");
475 BEAST_EXPECTS(
476 feature.isMember(jss::threshold),
477 feature[jss::name].asString() + " threshold");
478 BEAST_EXPECTS(
479 feature.isMember(jss::validations),
480 feature[jss::name].asString() + " validations");
481 BEAST_EXPECT(
482 feature[jss::count] ==
483 ((expectVeto || expectObsolete) ? 0 : 1));
484 BEAST_EXPECT(feature[jss::threshold] == 1);
485 BEAST_EXPECT(feature[jss::validations] == 1);
486 BEAST_EXPECTS(
487 expectVeto || expectObsolete || feature[jss::majority] == 2540,
488 "Majority: " + feature[jss::majority].asString());
489 }
490 }
491
492 void
494 {
495 testcase("Veto");
496
497 using namespace test::jtx;
498 Env env{*this, FeatureBitset(featureMultiSignReserve)};
499 constexpr char const* featureName = "MultiSignReserve";
500
501 auto jrr = env.rpc("feature", featureName)[jss::result];
502 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
503 return;
504 jrr.removeMember(jss::status);
505 if (!BEAST_EXPECT(jrr.size() == 1))
506 return;
507 auto feature = *(jrr.begin());
508 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
509 BEAST_EXPECTS(
510 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
511 "vetoed");
512
513 jrr = env.rpc("feature", featureName, "reject")[jss::result];
514 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
515 return;
516 jrr.removeMember(jss::status);
517 if (!BEAST_EXPECT(jrr.size() == 1))
518 return;
519 feature = *(jrr.begin());
520 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
521 BEAST_EXPECTS(
522 feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
523 "vetoed");
524
525 jrr = env.rpc("feature", featureName, "accept")[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 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 // anything other than accept or reject is an error
538 jrr = env.rpc("feature", featureName, "maybe");
539 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
540 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
541 }
542
543 void
545 {
546 testcase("Obsolete");
547
548 using namespace test::jtx;
549 Env env{*this};
550 constexpr char const* featureName = "NonFungibleTokensV1";
551
552 auto jrr = env.rpc("feature", featureName)[jss::result];
553 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
554 return;
555 jrr.removeMember(jss::status);
556 if (!BEAST_EXPECT(jrr.size() == 1))
557 return;
558 auto feature = *(jrr.begin());
559 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
560 BEAST_EXPECTS(
561 feature[jss::vetoed].isString() &&
562 feature[jss::vetoed].asString() == "Obsolete",
563 "vetoed");
564
565 jrr = env.rpc("feature", featureName, "reject")[jss::result];
566 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
567 return;
568 jrr.removeMember(jss::status);
569 if (!BEAST_EXPECT(jrr.size() == 1))
570 return;
571 feature = *(jrr.begin());
572 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
573 BEAST_EXPECTS(
574 feature[jss::vetoed].isString() &&
575 feature[jss::vetoed].asString() == "Obsolete",
576 "vetoed");
577
578 jrr = env.rpc("feature", featureName, "accept")[jss::result];
579 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
580 return;
581 jrr.removeMember(jss::status);
582 if (!BEAST_EXPECT(jrr.size() == 1))
583 return;
584 feature = *(jrr.begin());
585 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
586 BEAST_EXPECTS(
587 feature[jss::vetoed].isString() &&
588 feature[jss::vetoed].asString() == "Obsolete",
589 "vetoed");
590
591 // anything other than accept or reject is an error
592 jrr = env.rpc("feature", featureName, "maybe");
593 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
594 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
595 }
596
597public:
598 void
599 run() override
600 {
603 testNoParams();
606 testNonAdmin();
609 testVeto();
610 testObsolete();
611 }
612};
613
614BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
615
616} // 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