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