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 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(allAmendments.contains(name) && allAmendments.at(name) == AmendmentSupport::Retired);
45 }
46 else
47 {
48 BEAST_EXPECT(allAmendments.contains(name) && allAmendments.at(name) == AmendmentSupport::Supported);
49 }
50 }
52 BEAST_EXPECT(up == xrpl::detail::numUpVotedAmendments());
53 }
54 {
55 std::size_t supported = 0, unsupported = 0, retired = 0;
56 for (auto const& [name, support] : allAmendments)
57 {
58 switch (support)
59 {
61 ++supported;
62 BEAST_EXPECT(supportedAmendments.contains(name));
63 break;
65 ++unsupported;
66 break;
68 ++retired;
69 break;
70 default:
71 fail("Unknown AmendmentSupport", __FILE__, __LINE__);
72 }
73 }
74
75 BEAST_EXPECT(supported + retired == supportedAmendments.size());
76 BEAST_EXPECT(allAmendments.size() - unsupported == supportedAmendments.size());
77 }
78 }
79
80 void
82 {
83 testcase("featureToName");
84
85 // Test all the supported features. In a perfect world, this would test
86 // FeatureCollections::featureNames, but that's private. Leave it that
87 // way.
88 auto const supported = xrpl::detail::supportedAmendments();
89
90 for (auto const& [feature, vote] : supported)
91 {
92 (void)vote;
93 auto const registered = getRegisteredFeature(feature);
94 if (BEAST_EXPECT(registered))
95 {
96 BEAST_EXPECT(featureToName(*registered) == feature);
97 BEAST_EXPECT(bitsetIndexToFeature(featureToBitsetIndex(*registered)) == *registered);
98 }
99 }
100
101 // Test an arbitrary unknown feature
102 uint256 zero{0};
103 BEAST_EXPECT(featureToName(zero) == to_string(zero));
104 BEAST_EXPECT(featureToName(zero) == "0000000000000000000000000000000000000000000000000000000000000000");
105
106 // Test looking up an unknown feature
107 BEAST_EXPECT(!getRegisteredFeature("unknown"));
108
109 // Test a random sampling of the variables. If any of these get retired
110 // or removed, swap out for any other feature.
111 BEAST_EXPECT(featureToName(fixRemoveNFTokenAutoTrustLine) == "fixRemoveNFTokenAutoTrustLine");
112 BEAST_EXPECT(featureToName(featureBatch) == "Batch");
113 BEAST_EXPECT(featureToName(featureDID) == "DID");
114 BEAST_EXPECT(featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
115 BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
116 }
117
118 void
120 {
121 testcase("No Params, None Enabled");
122
123 using namespace test::jtx;
124 Env env{*this};
125
127
128 auto jrr = env.rpc("feature")[jss::result];
129 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
130 return;
131 for (auto const& feature : jrr[jss::features])
132 {
133 if (!BEAST_EXPECT(feature.isMember(jss::name)))
134 return;
135 // default config - so all should be disabled, and
136 // supported. Some may be vetoed.
137 bool expectVeto = (votes.at(feature[jss::name].asString()) == VoteBehavior::DefaultNo);
138 bool expectObsolete = (votes.at(feature[jss::name].asString()) == VoteBehavior::Obsolete);
139 BEAST_EXPECTS(
140 feature.isMember(jss::enabled) && !feature[jss::enabled].asBool(),
141 feature[jss::name].asString() + " enabled");
142 BEAST_EXPECTS(
143 feature.isMember(jss::vetoed) && feature[jss::vetoed].isBool() == !expectObsolete &&
144 (!feature[jss::vetoed].isBool() || feature[jss::vetoed].asBool() == expectVeto) &&
145 (feature[jss::vetoed].isBool() || feature[jss::vetoed].asString() == "Obsolete"),
146 feature[jss::name].asString() + " vetoed");
147 BEAST_EXPECTS(
148 feature.isMember(jss::supported) && feature[jss::supported].asBool(),
149 feature[jss::name].asString() + " supported");
150 }
151 }
152
153 void
155 {
156 testcase("Feature Param");
157
158 using namespace test::jtx;
159 Env env{*this};
160
161 auto jrr = env.rpc("feature", "fixAMMOverflowOffer")[jss::result];
162 BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
163 jrr.removeMember(jss::status);
164 BEAST_EXPECT(jrr.size() == 1);
165 BEAST_EXPECT(
166 jrr.isMember("12523DF04B553A0B1AD74F42DDB741DE8DC06A03FC089A0EF197E"
167 "2A87F1D8107"));
168 auto feature = *(jrr.begin());
169
170 BEAST_EXPECTS(feature[jss::name] == "fixAMMOverflowOffer", "name");
171 BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
172 BEAST_EXPECTS(feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(), "vetoed");
173 BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
174
175 // feature names are case-sensitive - expect error here
176 jrr = env.rpc("feature", "fMM")[jss::result];
177 BEAST_EXPECT(jrr[jss::error] == "badFeature");
178 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
179 }
180
181 void
183 {
184 testcase("Invalid Feature");
185
186 using namespace test::jtx;
187 Env env{*this};
188
189 auto testInvalidParam = [&](auto const& param) {
190 Json::Value params;
191 params[jss::feature] = param;
192 auto jrr = env.rpc("json", "feature", to_string(params))[jss::result];
193 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
194 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
195 };
196
197 testInvalidParam(1);
198 testInvalidParam(1.1);
199 testInvalidParam(true);
200 testInvalidParam(Json::Value(Json::nullValue));
201 testInvalidParam(Json::Value(Json::objectValue));
202 testInvalidParam(Json::Value(Json::arrayValue));
203
204 {
205 auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
206 BEAST_EXPECT(jrr[jss::error] == "badFeature");
207 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
208 }
209 }
210
211 void
213 {
214 testcase("Feature Without Admin");
215
216 using namespace test::jtx;
217 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
218 (*cfg)["port_rpc"].set("admin", "");
219 (*cfg)["port_ws"].set("admin", "");
220 return cfg;
221 })};
222
223 {
224 auto result = env.rpc("feature")[jss::result];
225 BEAST_EXPECT(result.isMember(jss::features));
226 // There should be at least 50 amendments. Don't do exact
227 // comparison to avoid maintenance as more amendments are added in
228 // the future.
229 BEAST_EXPECT(result[jss::features].size() >= 50);
230 for (auto it = result[jss::features].begin(); it != result[jss::features].end(); ++it)
231 {
232 uint256 id;
233 (void)id.parseHex(it.key().asString().c_str());
234 if (!BEAST_EXPECT((*it).isMember(jss::name)))
235 return;
236 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
237 bool expectSupported = env.app().getAmendmentTable().isSupported(id);
238 BEAST_EXPECTS(
239 (*it).isMember(jss::enabled) && (*it)[jss::enabled].asBool() == expectEnabled,
240 (*it)[jss::name].asString() + " enabled");
241 BEAST_EXPECTS(
242 (*it).isMember(jss::supported) && (*it)[jss::supported].asBool() == expectSupported,
243 (*it)[jss::name].asString() + " supported");
244 BEAST_EXPECT(!(*it).isMember(jss::vetoed));
245 BEAST_EXPECT(!(*it).isMember(jss::majority));
246 BEAST_EXPECT(!(*it).isMember(jss::count));
247 BEAST_EXPECT(!(*it).isMember(jss::validations));
248 BEAST_EXPECT(!(*it).isMember(jss::threshold));
249 }
250 }
251
252 {
253 Json::Value params;
254 // invalid feature
255 params[jss::feature] =
256 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
257 "EF";
258 auto const result = env.rpc("json", "feature", to_string(params))[jss::result];
259 BEAST_EXPECTS(result[jss::error] == "badFeature", result.toStyledString());
260 BEAST_EXPECT(result[jss::error_message] == "Feature unknown or invalid.");
261 }
262
263 {
264 Json::Value params;
265 params[jss::feature] =
266 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
267 "A7";
268 // invalid param
269 params[jss::vetoed] = true;
270 auto const result = env.rpc("json", "feature", to_string(params))[jss::result];
271 BEAST_EXPECTS(result[jss::error] == "noPermission", result[jss::error].asString());
272 BEAST_EXPECT(result[jss::error_message] == "You don't have permission for this command.");
273 }
274 }
275
276 void
278 {
279 testcase("No Params, Some Enabled");
280
281 using namespace test::jtx;
282 Env env{*this, FeatureBitset{}};
283
285
286 auto jrr = env.rpc("feature")[jss::result];
287 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
288 return;
289 for (auto it = jrr[jss::features].begin(); it != jrr[jss::features].end(); ++it)
290 {
291 uint256 id;
292 (void)id.parseHex(it.key().asString().c_str());
293 if (!BEAST_EXPECT((*it).isMember(jss::name)))
294 return;
295 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
296 bool expectSupported = env.app().getAmendmentTable().isSupported(id);
297 bool expectVeto = (votes.at((*it)[jss::name].asString()) == VoteBehavior::DefaultNo);
298 bool expectObsolete = (votes.at((*it)[jss::name].asString()) == VoteBehavior::Obsolete);
299 BEAST_EXPECTS(
300 (*it).isMember(jss::enabled) && (*it)[jss::enabled].asBool() == expectEnabled,
301 (*it)[jss::name].asString() + " enabled");
302 if (expectEnabled)
303 BEAST_EXPECTS(!(*it).isMember(jss::vetoed), (*it)[jss::name].asString() + " vetoed");
304 else
305 BEAST_EXPECTS(
306 (*it).isMember(jss::vetoed) && (*it)[jss::vetoed].isBool() == !expectObsolete &&
307 (!(*it)[jss::vetoed].isBool() || (*it)[jss::vetoed].asBool() == expectVeto) &&
308 ((*it)[jss::vetoed].isBool() || (*it)[jss::vetoed].asString() == "Obsolete"),
309 (*it)[jss::name].asString() + " vetoed");
310 BEAST_EXPECTS(
311 (*it).isMember(jss::supported) && (*it)[jss::supported].asBool() == expectSupported,
312 (*it)[jss::name].asString() + " supported");
313 }
314 }
315
316 void
318 {
319 testcase("With Majorities");
320
321 using namespace test::jtx;
322 Env env{*this, envconfig(validator, "")};
323
324 auto jrr = env.rpc("feature")[jss::result];
325 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
326 return;
327
328 // at this point, there are no majorities so no fields related to
329 // amendment voting
330 for (auto const& feature : jrr[jss::features])
331 {
332 if (!BEAST_EXPECT(feature.isMember(jss::name)))
333 return;
334 BEAST_EXPECTS(!feature.isMember(jss::majority), feature[jss::name].asString() + " majority");
335 BEAST_EXPECTS(!feature.isMember(jss::count), feature[jss::name].asString() + " count");
336 BEAST_EXPECTS(!feature.isMember(jss::threshold), feature[jss::name].asString() + " threshold");
337 BEAST_EXPECTS(!feature.isMember(jss::validations), feature[jss::name].asString() + " validations");
338 BEAST_EXPECTS(!feature.isMember(jss::vote), feature[jss::name].asString() + " vote");
339 }
340
341 auto majorities = getMajorityAmendments(*env.closed());
342 if (!BEAST_EXPECT(majorities.empty()))
343 return;
344
345 // close ledgers until the amendments show up.
346 for (auto i = 0; i <= 256; ++i)
347 {
348 env.close();
349 majorities = getMajorityAmendments(*env.closed());
350 if (!majorities.empty())
351 break;
352 }
353
354 // There should be at least 2 amendments. Don't do exact comparison
355 // to avoid maintenance as more amendments are added in the future.
356 BEAST_EXPECT(majorities.size() >= 2);
358
359 jrr = env.rpc("feature")[jss::result];
360 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
361 return;
362 for (auto const& feature : jrr[jss::features])
363 {
364 if (!BEAST_EXPECT(feature.isMember(jss::name)))
365 return;
366 bool expectVeto = (votes.at(feature[jss::name].asString()) == VoteBehavior::DefaultNo);
367 bool expectObsolete = (votes.at(feature[jss::name].asString()) == VoteBehavior::Obsolete);
368 BEAST_EXPECTS(
369 (expectVeto || expectObsolete) ^ feature.isMember(jss::majority),
370 feature[jss::name].asString() + " majority");
371 BEAST_EXPECTS(
372 feature.isMember(jss::vetoed) && feature[jss::vetoed].isBool() == !expectObsolete &&
373 (!feature[jss::vetoed].isBool() || feature[jss::vetoed].asBool() == expectVeto) &&
374 (feature[jss::vetoed].isBool() || feature[jss::vetoed].asString() == "Obsolete"),
375 feature[jss::name].asString() + " vetoed");
376 BEAST_EXPECTS(feature.isMember(jss::count), feature[jss::name].asString() + " count");
377 BEAST_EXPECTS(feature.isMember(jss::threshold), feature[jss::name].asString() + " threshold");
378 BEAST_EXPECTS(feature.isMember(jss::validations), feature[jss::name].asString() + " validations");
379 BEAST_EXPECT(feature[jss::count] == ((expectVeto || expectObsolete) ? 0 : 1));
380 BEAST_EXPECT(feature[jss::threshold] == 1);
381 BEAST_EXPECT(feature[jss::validations] == 1);
382 BEAST_EXPECTS(
383 expectVeto || expectObsolete || feature[jss::majority] == 2540,
384 "Majority: " + feature[jss::majority].asString());
385 }
386 }
387
388 void
390 {
391 testcase("Veto");
392
393 using namespace test::jtx;
394 Env env{*this, FeatureBitset{featurePriceOracle}};
395 constexpr char const* featureName = "fixAMMOverflowOffer";
396
397 auto jrr = env.rpc("feature", featureName)[jss::result];
398 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
399 return;
400 jrr.removeMember(jss::status);
401 if (!BEAST_EXPECT(jrr.size() == 1))
402 return;
403 auto feature = *(jrr.begin());
404 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
405 BEAST_EXPECTS(feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(), "vetoed");
406
407 jrr = env.rpc("feature", featureName, "reject")[jss::result];
408 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
409 return;
410 jrr.removeMember(jss::status);
411 if (!BEAST_EXPECT(jrr.size() == 1))
412 return;
413 feature = *(jrr.begin());
414 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
415 BEAST_EXPECTS(feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(), "vetoed");
416
417 jrr = env.rpc("feature", featureName, "accept")[jss::result];
418 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
419 return;
420 jrr.removeMember(jss::status);
421 if (!BEAST_EXPECT(jrr.size() == 1))
422 return;
423 feature = *(jrr.begin());
424 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
425 BEAST_EXPECTS(feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(), "vetoed");
426
427 // anything other than accept or reject is an error
428 jrr = env.rpc("feature", featureName, "maybe");
429 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
430 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
431 }
432
433 void
435 {
436 testcase("Obsolete");
437
438 using namespace test::jtx;
439 Env env{*this};
440
441 auto const& supportedAmendments = detail::supportedAmendments();
442 auto obsoleteFeature =
443 std::find_if(std::begin(supportedAmendments), std::end(supportedAmendments), [](auto const& pair) {
444 return pair.second == VoteBehavior::Obsolete;
445 });
446
447 if (obsoleteFeature == std::end(supportedAmendments))
448 {
449 pass();
450 return;
451 }
452
453 auto const featureName = obsoleteFeature->first;
454
455 auto jrr = env.rpc("feature", featureName)[jss::result];
456 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
457 return;
458 jrr.removeMember(jss::status);
459 if (!BEAST_EXPECT(jrr.size() == 1))
460 return;
461 auto feature = *(jrr.begin());
462 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
463 BEAST_EXPECTS(feature[jss::vetoed].isString() && feature[jss::vetoed].asString() == "Obsolete", "vetoed");
464
465 jrr = env.rpc("feature", featureName, "reject")[jss::result];
466 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
467 return;
468 jrr.removeMember(jss::status);
469 if (!BEAST_EXPECT(jrr.size() == 1))
470 return;
471 feature = *(jrr.begin());
472 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
473 BEAST_EXPECTS(feature[jss::vetoed].isString() && feature[jss::vetoed].asString() == "Obsolete", "vetoed");
474
475 jrr = env.rpc("feature", featureName, "accept")[jss::result];
476 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
477 return;
478 jrr.removeMember(jss::status);
479 if (!BEAST_EXPECT(jrr.size() == 1))
480 return;
481 feature = *(jrr.begin());
482 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
483 BEAST_EXPECTS(feature[jss::vetoed].isString() && feature[jss::vetoed].asString() == "Obsolete", "vetoed");
484
485 // anything other than accept or reject is an error
486 jrr = env.rpc("feature", featureName, "maybe");
487 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
488 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
489 }
490
491public:
492 void
493 run() override
494 {
497 testNoParams();
500 testNonAdmin();
503 testVeto();
504 testObsolete();
505 }
506};
507
508BEAST_DEFINE_TESTSUITE(Feature, rpc, xrpl);
509
510} // namespace xrpl
T at(T... args)
T begin(T... args)
Represents a JSON value.
Definition json_value.h:131
A testsuite class.
Definition suite.h:52
void pass()
Record a successful test condition.
Definition suite.h:495
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:148
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:517
void run() override
Runs the suite.
T end(T... args)
T find_if(T... args)
@ 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 numDownVotedAmendments()
Amendments that this server won't vote for by default.
Definition Feature.cpp:321
std::map< std::string, VoteBehavior > const & supportedAmendments()
Amendments that this server supports and the default voting behavior.
Definition Feature.cpp:314
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition Feature.cpp:328
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
size_t featureToBitsetIndex(uint256 const &f)
Definition Feature.cpp:363
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:856
std::map< std::string, AmendmentSupport > const & allAmendments()
All amendments libxrpl knows about.
Definition Feature.cpp:305
std::string featureToName(uint256 const &f)
Definition Feature.cpp:375
uint256 bitsetIndexToFeature(size_t i)
Definition Feature.cpp:369
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition Feature.cpp:336