xrpld
Loading...
Searching...
No Matches
ValidatorRPC_test.cpp
1#include <test/jtx/Env.h>
2#include <test/jtx/TrustedPublisherServer.h>
3#include <test/jtx/envconfig.h>
4
5#include <xrpld/app/main/BasicApp.h>
6#include <xrpld/app/misc/ValidatorSite.h>
7#include <xrpld/core/Config.h>
8
9#include <xrpl/basics/UnorderedContainers.h>
10#include <xrpl/basics/chrono.h>
11#include <xrpl/basics/strHex.h>
12#include <xrpl/beast/unit_test/suite.h>
13#include <xrpl/config/Constants.h>
14#include <xrpl/json/json_value.h>
15#include <xrpl/protocol/KeyType.h>
16#include <xrpl/protocol/PublicKey.h>
17#include <xrpl/protocol/SecretKey.h>
18#include <xrpl/protocol/UintTypes.h>
19#include <xrpl/protocol/jss.h>
20#include <xrpl/protocol/tokens.h>
21
22#include <chrono>
23#include <cstdint>
24#include <limits>
25#include <memory>
26#include <set>
27#include <sstream>
28#include <string>
29#include <vector>
30
31namespace xrpl::test {
32
34{
36
37public:
38 void
40 {
41 using namespace test::jtx;
42
43 for (bool const isAdmin : {true, false})
44 {
45 for (std::string const cmd : {"validators", "validator_list_sites"})
46 {
47 Env env{*this, isAdmin ? envconfig() : envconfig(noAdmin)};
48 env.setRetries(isAdmin ? 5 : 0);
49 auto const jrr = env.rpc(cmd)[jss::result];
50 if (isAdmin)
51 {
52 BEAST_EXPECT(!jrr.isMember(jss::error));
53 BEAST_EXPECT(jrr[jss::status] == "success");
54 }
55 else
56 {
57 // The current HTTP/S ServerHandler returns an HTTP 403
58 // error code here rather than a noPermission JSON error.
59 // The JSONRPCClient just eats that error and returns null
60 // result.
61 BEAST_EXPECT(jrr.isNull());
62 }
63 }
64
65 {
66 Env env{*this, isAdmin ? envconfig() : envconfig(noAdmin)};
67 auto const jrr = env.rpc("server_info")[jss::result];
68 BEAST_EXPECT(jrr[jss::status] == "success");
69 BEAST_EXPECT(jrr[jss::info].isMember(jss::validator_list) == isAdmin);
70 }
71
72 {
73 Env env{*this, isAdmin ? envconfig() : envconfig(noAdmin)};
74 auto const jrr = env.rpc("server_state")[jss::result];
75 BEAST_EXPECT(jrr[jss::status] == "success");
76 BEAST_EXPECT(jrr[jss::state].isMember(jss::validator_list_expires) == isAdmin);
77 }
78 }
79 }
80
81 void
83 {
84 using namespace test::jtx;
85
86 std::set<std::string> const keys = {
87 "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7",
88 "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj"};
89 Env env{
90 *this,
92 for (auto const& key : keys)
93 cfg->section(Sections::kValidators).append(key);
94 return cfg;
95 }),
96 };
97
98 // Server info reports maximum expiration since not dynamic
99 {
100 auto const jrr = env.rpc("server_info")[jss::result];
101 BEAST_EXPECT(jrr[jss::info][jss::validator_list][jss::expiration] == "never");
102 }
103 {
104 auto const jrr = env.rpc("server_state")[jss::result];
105 BEAST_EXPECT(
106 jrr[jss::state][jss::validator_list_expires].asUInt() ==
107 NetClock::time_point::max().time_since_epoch().count());
108 }
109 // All our keys are in the response
110 {
111 auto const jrr = env.rpc("validators")[jss::result];
112 BEAST_EXPECT(jrr[jss::validator_list][jss::expiration] == "never");
113 BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == keys.size());
114 BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == keys.size());
115 BEAST_EXPECT(jrr[jss::publisher_lists].size() == 0);
116 BEAST_EXPECT(jrr[jss::local_static_keys].size() == keys.size());
117 for (auto const& jKey : jrr[jss::local_static_keys])
118 {
119 BEAST_EXPECT(keys.count(jKey.asString()) == 1);
120 }
121 BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
122 }
123 // No validator sites configured
124 {
125 auto const jrr = env.rpc("validator_list_sites")[jss::result];
126 BEAST_EXPECT(jrr[jss::validator_sites].size() == 0);
127 }
128 // Negative UNL empty
129 {
130 auto const jrr = env.rpc("validators")[jss::result];
131 BEAST_EXPECT(jrr[jss::NegativeUNL].isNull());
132 }
133 // Negative UNL update
134 {
135 hash_set<PublicKey> disabledKeys;
136 auto k1 = randomKeyPair(KeyType::Ed25519).first;
137 auto k2 = randomKeyPair(KeyType::Ed25519).first;
138 disabledKeys.insert(k1);
139 disabledKeys.insert(k2);
140 env.app().getValidators().setNegativeUNL(disabledKeys);
141
142 auto const jrr = env.rpc("validators")[jss::result];
143 auto& jrrnUnl = jrr[jss::NegativeUNL];
144 auto jrrnUnlSize = jrrnUnl.size();
145 BEAST_EXPECT(jrrnUnlSize == 2);
146 for (std::uint32_t x = 0; x < jrrnUnlSize; ++x)
147 {
148 auto parsedKey =
149 parseBase58<PublicKey>(TokenType::NodePublic, jrrnUnl[x].asString());
150 BEAST_EXPECT(parsedKey);
151 if (parsedKey)
152 BEAST_EXPECT(disabledKeys.contains(*parsedKey));
153 }
154
155 disabledKeys.clear();
156 env.app().getValidators().setNegativeUNL(disabledKeys);
157 auto const jrrUpdated = env.rpc("validators")[jss::result];
158 BEAST_EXPECT(jrrUpdated[jss::NegativeUNL].isNull());
159 }
160 }
161
162 void
164 {
165 using namespace test::jtx;
166
167 auto toStr = [](PublicKey const& publicKey) {
168 return toBase58(TokenType::NodePublic, publicKey);
169 };
170
171 // Validator keys that will be in the published list
172 std::vector<Validator> const validators = {
174 std::set<std::string> expectedKeys;
175 for (auto const& val : validators)
176 expectedKeys.insert(toStr(val.masterPublic));
177
178 // Manage single-thread io_context for server.
179 BasicApp worker{1};
180 using namespace std::chrono_literals;
181 NetClock::time_point const validUntil{3600s};
182 NetClock::time_point const validFrom2{validUntil - 60s};
183 NetClock::time_point const validUntil2{validFrom2 + 3600s};
184 auto server = makeTrustedPublisherServer(
185 worker.getIoContext(),
186 validators,
187 validUntil,
188 {{validFrom2, validUntil2}},
189 false,
190 1,
191 false);
192
193 //----------------------------------------------------------------------
194 // Publisher list site unavailable v1
195 {
196 // Publisher site information
197 using namespace std::string_literals;
198 std::string siteURI = "http://"s + getEnvLocalhostAddr() + ":1234/validators";
199
200 Env env{
201 *this,
203 cfg->section(Sections::kValidatorListSites).append(siteURI);
204 cfg->section(Sections::kValidatorListKeys)
205 .append(strHex(server->publisherPublic()));
206 return cfg;
207 }),
208 };
209
210 env.app().getValidatorSites().start();
211 env.app().getValidatorSites().join();
212
213 {
214 auto const jrr = env.rpc("server_info")[jss::result];
215 BEAST_EXPECT(jrr[jss::info][jss::validator_list][jss::expiration] == "unknown");
216 }
217 {
218 auto const jrr = env.rpc("server_state")[jss::result];
219 BEAST_EXPECT(jrr[jss::state][jss::validator_list_expires].asInt() == 0);
220 }
221 {
222 auto const jrr = env.rpc("validators")[jss::result];
223 BEAST_EXPECT(
224 jrr[jss::validation_quorum].asUInt() ==
226 BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
227 BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == 0);
228 BEAST_EXPECT(jrr[jss::validator_list][jss::expiration] == "unknown");
229
230 if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
231 {
232 auto jp = jrr[jss::publisher_lists][0u];
233 BEAST_EXPECT(jp[jss::available] == false);
234 BEAST_EXPECT(jp[jss::list].size() == 0);
235 BEAST_EXPECT(!jp.isMember(jss::seq));
236 BEAST_EXPECT(!jp.isMember(jss::expiration));
237 BEAST_EXPECT(!jp.isMember(jss::version));
238 BEAST_EXPECT(jp[jss::pubkey_publisher] == strHex(server->publisherPublic()));
239 }
240 BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
241 }
242 {
243 auto const jrr = env.rpc("validator_list_sites")[jss::result];
244 if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
245 {
246 auto js = jrr[jss::validator_sites][0u];
247 BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
248 BEAST_EXPECT(js[jss::uri] == siteURI);
249 BEAST_EXPECT(js.isMember(jss::last_refresh_time));
250 BEAST_EXPECT(js[jss::last_refresh_status] == "invalid");
251 }
252 }
253 }
254 // Publisher list site unavailable v2
255 {
256 // Publisher site information
257 using namespace std::string_literals;
258 std::string siteURI = "http://"s + getEnvLocalhostAddr() + ":1234/validators2";
259
260 Env env{
261 *this,
262 envconfig([&](std::unique_ptr<Config> cfg) {
263 cfg->section(Sections::kValidatorListSites).append(siteURI);
264 cfg->section(Sections::kValidatorListKeys)
265 .append(strHex(server->publisherPublic()));
266 return cfg;
267 }),
268 };
269
270 env.app().getValidatorSites().start();
271 env.app().getValidatorSites().join();
272
273 {
274 auto const jrr = env.rpc("server_info")[jss::result];
275 BEAST_EXPECT(jrr[jss::info][jss::validator_list][jss::expiration] == "unknown");
276 }
277 {
278 auto const jrr = env.rpc("server_state")[jss::result];
279 BEAST_EXPECT(jrr[jss::state][jss::validator_list_expires].asInt() == 0);
280 }
281 {
282 auto const jrr = env.rpc("validators")[jss::result];
283 BEAST_EXPECT(
284 jrr[jss::validation_quorum].asUInt() ==
286 BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
287 BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == 0);
288 BEAST_EXPECT(jrr[jss::validator_list][jss::expiration] == "unknown");
289
290 if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
291 {
292 auto jp = jrr[jss::publisher_lists][0u];
293 BEAST_EXPECT(jp[jss::available] == false);
294 BEAST_EXPECT(jp[jss::list].size() == 0);
295 BEAST_EXPECT(!jp.isMember(jss::seq));
296 BEAST_EXPECT(!jp.isMember(jss::expiration));
297 BEAST_EXPECT(!jp.isMember(jss::version));
298 BEAST_EXPECT(jp[jss::pubkey_publisher] == strHex(server->publisherPublic()));
299 }
300 BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
301 }
302 {
303 auto const jrr = env.rpc("validator_list_sites")[jss::result];
304 if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
305 {
306 auto js = jrr[jss::validator_sites][0u];
307 BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
308 BEAST_EXPECT(js[jss::uri] == siteURI);
309 BEAST_EXPECT(js.isMember(jss::last_refresh_time));
310 BEAST_EXPECT(js[jss::last_refresh_status] == "invalid");
311 }
312 }
313 }
314 //----------------------------------------------------------------------
315 // Publisher list site available
316 server->start();
317 // Publisher list site available v1
318 {
319 std::stringstream uri;
320 uri << "http://" << server->localEndpoint() << "/validators";
321 auto siteURI = uri.str();
322
323 Env env{
324 *this,
325 envconfig([&](std::unique_ptr<Config> cfg) {
326 cfg->section(Sections::kValidatorListSites).append(siteURI);
327 cfg->section(Sections::kValidatorListKeys)
328 .append(strHex(server->publisherPublic()));
329 return cfg;
330 }),
331 };
332
333 env.app().getValidatorSites().start();
334 env.app().getValidatorSites().join();
335 hash_set<NodeID> startKeys;
336 for (auto const& val : validators)
337 startKeys.insert(calcNodeID(val.masterPublic));
338
339 env.app().getValidators().updateTrusted(
340 startKeys,
341 env.timeKeeper().now(),
342 env.app().getOPs(),
343 env.app().getOverlay(),
344 env.app().getHashRouter());
345
346 {
347 auto const jrr = env.rpc("server_info")[jss::result];
348 BEAST_EXPECT(
349 jrr[jss::info][jss::validator_list][jss::expiration] == to_string(validUntil));
350 }
351 {
352 auto const jrr = env.rpc("server_state")[jss::result];
353 BEAST_EXPECT(
354 jrr[jss::state][jss::validator_list_expires].asUInt() ==
355 validUntil.time_since_epoch().count());
356 }
357 {
358 auto const jrr = env.rpc("validators")[jss::result];
359 BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == 2);
360 BEAST_EXPECT(jrr[jss::validator_list][jss::expiration] == to_string(validUntil));
361 BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
362
363 BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == expectedKeys.size());
364 for (auto const& jKey : jrr[jss::trusted_validator_keys])
365 {
366 BEAST_EXPECT(expectedKeys.count(jKey.asString()) == 1);
367 }
368
369 if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
370 {
371 auto jp = jrr[jss::publisher_lists][0u];
372 BEAST_EXPECT(jp[jss::available] == true);
373 if (BEAST_EXPECT(jp[jss::list].size() == 2))
374 {
375 // check entries
376 std::set<std::string> foundKeys;
377 for (auto const& k : jp[jss::list])
378 {
379 foundKeys.insert(k.asString());
380 }
381 BEAST_EXPECT(foundKeys == expectedKeys);
382 }
383 BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
384 BEAST_EXPECT(jp[jss::pubkey_publisher] == strHex(server->publisherPublic()));
385 BEAST_EXPECT(jp[jss::expiration] == to_string(validUntil));
386 BEAST_EXPECT(jp[jss::version] == 1);
387 }
388 auto jsk = jrr[jss::signing_keys];
389 BEAST_EXPECT(jsk.size() == 2);
390 for (auto const& val : validators)
391 {
392 BEAST_EXPECT(jsk.isMember(toStr(val.masterPublic)));
393 BEAST_EXPECT(jsk[toStr(val.masterPublic)] == toStr(val.signingPublic));
394 }
395 }
396 {
397 auto const jrr = env.rpc("validator_list_sites")[jss::result];
398 if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
399 {
400 auto js = jrr[jss::validator_sites][0u];
401 BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
402 BEAST_EXPECT(js[jss::uri] == siteURI);
403 BEAST_EXPECT(js[jss::last_refresh_status] == "accepted");
404 // The actual time of the update will vary run to run, so
405 // just verify the time is there
406 BEAST_EXPECT(js.isMember(jss::last_refresh_time));
407 }
408 }
409 }
410 // Publisher list site available v2
411 {
412 std::stringstream uri;
413 uri << "http://" << server->localEndpoint() << "/validators2";
414 auto siteURI = uri.str();
415
416 Env env{
417 *this,
418 envconfig([&](std::unique_ptr<Config> cfg) {
419 cfg->section(Sections::kValidatorListSites).append(siteURI);
420 cfg->section(Sections::kValidatorListKeys)
421 .append(strHex(server->publisherPublic()));
422 return cfg;
423 }),
424 };
425
426 env.app().getValidatorSites().start();
427 env.app().getValidatorSites().join();
428 hash_set<NodeID> startKeys;
429 for (auto const& val : validators)
430 startKeys.insert(calcNodeID(val.masterPublic));
431
432 env.app().getValidators().updateTrusted(
433 startKeys,
434 env.timeKeeper().now(),
435 env.app().getOPs(),
436 env.app().getOverlay(),
437 env.app().getHashRouter());
438
439 {
440 auto const jrr = env.rpc("server_info")[jss::result];
441 BEAST_EXPECT(
442 jrr[jss::info][jss::validator_list][jss::expiration] == to_string(validUntil2));
443 }
444 {
445 auto const jrr = env.rpc("server_state")[jss::result];
446 BEAST_EXPECT(
447 jrr[jss::state][jss::validator_list_expires].asUInt() ==
448 validUntil2.time_since_epoch().count());
449 }
450 {
451 auto const jrr = env.rpc("validators")[jss::result];
452 BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == 2);
453 BEAST_EXPECT(jrr[jss::validator_list][jss::expiration] == to_string(validUntil2));
454 BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
455
456 BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == expectedKeys.size());
457 for (auto const& jKey : jrr[jss::trusted_validator_keys])
458 {
459 BEAST_EXPECT(expectedKeys.count(jKey.asString()) == 1);
460 }
461
462 if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
463 {
464 auto jp = jrr[jss::publisher_lists][0u];
465 BEAST_EXPECT(jp[jss::available] == true);
466 if (BEAST_EXPECT(jp[jss::list].size() == 2))
467 {
468 // check entries
469 std::set<std::string> foundKeys;
470 for (auto const& k : jp[jss::list])
471 {
472 foundKeys.insert(k.asString());
473 }
474 BEAST_EXPECT(foundKeys == expectedKeys);
475 }
476 BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
477 BEAST_EXPECT(jp[jss::pubkey_publisher] == strHex(server->publisherPublic()));
478 BEAST_EXPECT(jp[jss::expiration] == to_string(validUntil));
479 BEAST_EXPECT(jp[jss::version] == 2);
480 if (BEAST_EXPECT(jp.isMember(jss::remaining)) &&
481 BEAST_EXPECT(jp[jss::remaining].isArray()) &&
482 BEAST_EXPECT(jp[jss::remaining].size() == 1))
483 {
484 auto const& r = jp[jss::remaining][0u];
485 if (BEAST_EXPECT(r[jss::list].size() == 2))
486 {
487 // check entries
488 std::set<std::string> foundKeys;
489 for (auto const& k : r[jss::list])
490 {
491 foundKeys.insert(k.asString());
492 }
493 BEAST_EXPECT(foundKeys == expectedKeys);
494 }
495 BEAST_EXPECT(r[jss::seq].asUInt() == 2);
496 BEAST_EXPECT(r[jss::effective] == to_string(validFrom2));
497 BEAST_EXPECT(r[jss::expiration] == to_string(validUntil2));
498 }
499 }
500 auto jsk = jrr[jss::signing_keys];
501 BEAST_EXPECT(jsk.size() == 2);
502 for (auto const& val : validators)
503 {
504 BEAST_EXPECT(jsk.isMember(toStr(val.masterPublic)));
505 BEAST_EXPECT(jsk[toStr(val.masterPublic)] == toStr(val.signingPublic));
506 }
507 }
508 {
509 auto const jrr = env.rpc("validator_list_sites")[jss::result];
510 if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
511 {
512 auto js = jrr[jss::validator_sites][0u];
513 BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
514 BEAST_EXPECT(js[jss::uri] == siteURI);
515 BEAST_EXPECT(js[jss::last_refresh_status] == "accepted");
516 // The actual time of the update will vary run to run, so
517 // just verify the time is there
518 BEAST_EXPECT(js.isMember(jss::last_refresh_time));
519 }
520 }
521 }
522 }
523
524 void
526 {
527 using namespace test::jtx;
528 Env env{*this};
529 auto result = env.rpc("validation_create");
530 BEAST_EXPECT(result.isMember(jss::result) && result[jss::result][jss::status] == "success");
531 result =
532 env.rpc("validation_create", "BAWL MAN JADE MOON DOVE GEM SON NOW HAD ADEN GLOW TIRE");
533 BEAST_EXPECT(result.isMember(jss::result) && result[jss::result][jss::status] == "success");
534 }
535
536 void
537 run() override
538 {
543 }
544};
545
546BEAST_DEFINE_TESTSUITE(ValidatorRPC, rpc, xrpl);
547
548} // namespace xrpl::test
A testsuite class.
Definition suite.h:50
UInt size() const
Number of values in array or object.
std::chrono::time_point< NetClock > time_point
Definition chrono.h:46
A public key.
Definition PublicKey.h:42
virtual ValidatorList & getValidators()=0
virtual ValidatorSite & getValidatorSites()=0
void setNegativeUNL(hash_set< PublicKey > const &negUnl)
set the Negative UNL with validators' master public keys
void join()
Wait for current fetches from sites to complete.
void start()
Start fetching lists from sites.
TrustedPublisherServer::Validator Validator
void run() override
Runs the suite.
A transaction testing environment.
Definition Env.h:143
Application & app()
Definition Env.h:280
void setRetries(unsigned r=5)
Definition Env.h:494
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:864
T clear(T... args)
T contains(T... args)
T count(T... args)
T insert(T... args)
std::uint32_t asUInt(AnyValue const &v)
Definition Oracle.cpp:398
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:28
std::unique_ptr< Config > noAdmin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition envconfig.cpp:64
std::shared_ptr< TrustedPublisherServer > makeTrustedPublisherServer(boost::asio::io_context &ioc, std::vector< TrustedPublisherServer::Validator > const &validators, NetClock::time_point validUntil, std::vector< std::pair< NetClock::time_point, NetClock::time_point > > const &futures, bool useSSL=false, int version=1, bool immediateStart=true, int sequence=1)
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
char const * getEnvLocalhostAddr()
Definition envconfig.h:10
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
bool isAdmin(Port const &port, json::Value const &params, beast::IP::Address const &remoteIp)
Definition Role.cpp:81
T size(T... args)
T str(T... args)
static constexpr auto kValidators
Definition Constants.h:73
static constexpr auto kValidatorListKeys
Definition Constants.h:69
static constexpr auto kValidatorListSites
Definition Constants.h:70