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