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(fixTrustLinesToSelf) == "fixTrustLinesToSelf");
125 BEAST_EXPECT(featureToName(featureFlow) == "Flow");
126 BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
127 BEAST_EXPECT(
128 featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
129 BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
130 }
131
132 void
134 {
135 testcase("No Params, None Enabled");
136
137 using namespace test::jtx;
138 Env env{*this};
139
142
143 auto jrr = env.rpc("feature")[jss::result];
144 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
145 return;
146 for (auto const& feature : jrr[jss::features])
147 {
148 if (!BEAST_EXPECT(feature.isMember(jss::name)))
149 return;
150 // default config - so all should be disabled, and
151 // supported. Some may be vetoed.
152 bool expectVeto =
153 (votes.at(feature[jss::name].asString()) ==
155 bool expectObsolete =
156 (votes.at(feature[jss::name].asString()) ==
158 BEAST_EXPECTS(
159 feature.isMember(jss::enabled) &&
160 !feature[jss::enabled].asBool(),
161 feature[jss::name].asString() + " enabled");
162 BEAST_EXPECTS(
163 feature.isMember(jss::vetoed) &&
164 feature[jss::vetoed].isBool() == !expectObsolete &&
165 (!feature[jss::vetoed].isBool() ||
166 feature[jss::vetoed].asBool() == expectVeto) &&
167 (feature[jss::vetoed].isBool() ||
168 feature[jss::vetoed].asString() == "Obsolete"),
169 feature[jss::name].asString() + " vetoed");
170 BEAST_EXPECTS(
171 feature.isMember(jss::supported) &&
172 feature[jss::supported].asBool(),
173 feature[jss::name].asString() + " supported");
174 }
175 }
176
177 void
179 {
180 testcase("Feature Param");
181
182 using namespace test::jtx;
183 Env env{*this};
184
185 auto jrr = env.rpc("feature", "MultiSignReserve")[jss::result];
186 BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
187 jrr.removeMember(jss::status);
188 BEAST_EXPECT(jrr.size() == 1);
189 BEAST_EXPECT(
190 jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
191 "1FC07EFE41D"));
192 auto feature = *(jrr.begin());
193
194 BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
195 BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
196 BEAST_EXPECTS(
197 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
198 "vetoed");
199 BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
200
201 // feature names are case-sensitive - expect error here
202 jrr = env.rpc("feature", "multiSignReserve")[jss::result];
203 BEAST_EXPECT(jrr[jss::error] == "badFeature");
204 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
205 }
206
207 void
209 {
210 testcase("Invalid Feature");
211
212 using namespace test::jtx;
213 Env env{*this};
214
215 auto testInvalidParam = [&](auto const& param) {
216 Json::Value params;
217 params[jss::feature] = param;
218 auto jrr =
219 env.rpc("json", "feature", to_string(params))[jss::result];
220 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
221 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
222 };
223
224 testInvalidParam(1);
225 testInvalidParam(1.1);
226 testInvalidParam(true);
227 testInvalidParam(Json::Value(Json::nullValue));
228 testInvalidParam(Json::Value(Json::objectValue));
229 testInvalidParam(Json::Value(Json::arrayValue));
230
231 {
232 auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
233 BEAST_EXPECT(jrr[jss::error] == "badFeature");
234 BEAST_EXPECT(
235 jrr[jss::error_message] == "Feature unknown or invalid.");
236 }
237 }
238
239 void
241 {
242 testcase("Feature Without Admin");
243
244 using namespace test::jtx;
245 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
246 (*cfg)["port_rpc"].set("admin", "");
247 (*cfg)["port_ws"].set("admin", "");
248 return cfg;
249 })};
250
251 {
252 auto result = env.rpc("feature")[jss::result];
253 BEAST_EXPECT(result.isMember(jss::features));
254 // There should be at least 50 amendments. Don't do exact
255 // comparison to avoid maintenance as more amendments are added in
256 // the future.
257 BEAST_EXPECT(result[jss::features].size() >= 50);
258 for (auto it = result[jss::features].begin();
259 it != result[jss::features].end();
260 ++it)
261 {
262 uint256 id;
263 (void)id.parseHex(it.key().asString().c_str());
264 if (!BEAST_EXPECT((*it).isMember(jss::name)))
265 return;
266 bool expectEnabled =
267 env.app().getAmendmentTable().isEnabled(id);
268 bool expectSupported =
269 env.app().getAmendmentTable().isSupported(id);
270 BEAST_EXPECTS(
271 (*it).isMember(jss::enabled) &&
272 (*it)[jss::enabled].asBool() == expectEnabled,
273 (*it)[jss::name].asString() + " enabled");
274 BEAST_EXPECTS(
275 (*it).isMember(jss::supported) &&
276 (*it)[jss::supported].asBool() == expectSupported,
277 (*it)[jss::name].asString() + " supported");
278 BEAST_EXPECT(!(*it).isMember(jss::vetoed));
279 BEAST_EXPECT(!(*it).isMember(jss::majority));
280 BEAST_EXPECT(!(*it).isMember(jss::count));
281 BEAST_EXPECT(!(*it).isMember(jss::validations));
282 BEAST_EXPECT(!(*it).isMember(jss::threshold));
283 }
284 }
285
286 {
287 Json::Value params;
288 // invalid feature
289 params[jss::feature] =
290 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
291 "EF";
292 auto const result =
293 env.rpc("json", "feature", to_string(params))[jss::result];
294 BEAST_EXPECTS(
295 result[jss::error] == "badFeature", result.toStyledString());
296 BEAST_EXPECT(
297 result[jss::error_message] == "Feature unknown or invalid.");
298 }
299
300 {
301 Json::Value params;
302 params[jss::feature] =
303 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
304 "A7";
305 // invalid param
306 params[jss::vetoed] = true;
307 auto const result =
308 env.rpc("json", "feature", to_string(params))[jss::result];
309 BEAST_EXPECTS(
310 result[jss::error] == "noPermission",
311 result[jss::error].asString());
312 BEAST_EXPECT(
313 result[jss::error_message] ==
314 "You don't have permission for this command.");
315 }
316 }
317
318 void
320 {
321 testcase("No Params, Some Enabled");
322
323 using namespace test::jtx;
324 Env env{
325 *this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
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:130
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:19
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
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