rippled
Loading...
Searching...
No Matches
AmendmentTable_test.cpp
1#include <test/jtx/Env.h>
2#include <test/unit_test/SuiteJournal.h>
3
4#include <xrpld/app/misc/AmendmentTable.h>
5#include <xrpld/core/ConfigSections.h>
6
7#include <xrpl/basics/BasicConfig.h>
8#include <xrpl/basics/Log.h>
9#include <xrpl/basics/chrono.h>
10#include <xrpl/beast/unit_test.h>
11#include <xrpl/protocol/Feature.h>
12#include <xrpl/protocol/PublicKey.h>
13#include <xrpl/protocol/STValidation.h>
14#include <xrpl/protocol/SecretKey.h>
15#include <xrpl/protocol/TxFlags.h>
16#include <xrpl/protocol/digest.h>
17#include <xrpl/protocol/jss.h>
18
19namespace xrpl {
20
22{
23private:
24 static uint256
26 {
29 hash_append(h, in);
30 auto const d = static_cast<sha256_hasher::result_type>(h);
31 uint256 result;
32 std::memcpy(result.data(), d.data(), d.size());
33 return result;
34 }
35
36 static Section
37 makeSection(std::string const& name, std::vector<std::string> const& amendments)
38 {
39 Section section(name);
40 for (auto const& a : amendments)
41 section.append(to_string(amendmentId(a)) + " " + a);
42 return section;
43 }
44
45 static Section
47 {
48 return makeSection("Test", amendments);
49 }
50
51 static Section
52 makeSection(uint256 const& amendment)
53 {
54 Section section("Test");
55 section.append(to_string(amendment) + " " + to_string(amendment));
56 return section;
57 }
58
61 {
62 auto cfg = test::jtx::envconfig();
63 cfg->section(SECTION_AMENDMENTS) = makeSection(SECTION_AMENDMENTS, enabled_);
64 cfg->section(SECTION_VETO_AMENDMENTS) = makeSection(SECTION_VETO_AMENDMENTS, vetoed_);
65 return cfg;
66 }
67
69 makeFeatureInfo(std::vector<std::string> const& amendments, VoteBehavior voteBehavior)
70 {
72 result.reserve(amendments.size());
73 for (auto const& a : amendments)
74 {
75 result.emplace_back(a, amendmentId(a), voteBehavior);
76 }
77 return result;
78 }
79
82 {
83 return makeFeatureInfo(amendments, VoteBehavior::DefaultYes);
84 }
85
87 makeDefaultYes(uint256 const amendment)
88 {
90 return result;
91 }
92
95 {
96 return makeFeatureInfo(amendments, VoteBehavior::DefaultNo);
97 }
98
101 {
102 return makeFeatureInfo(amendments, VoteBehavior::Obsolete);
103 }
104
105 template <class Arg, class... Args>
106 static size_t
107 totalsize(std::vector<Arg> const& src, Args const&... args)
108 {
109 if constexpr (sizeof...(args) > 0)
110 return src.size() + totalsize(args...);
111 return src.size();
112 }
113
114 template <class Arg, class... Args>
115 static void
116 combine_arg(std::vector<Arg>& dest, std::vector<Arg> const& src, Args const&... args)
117 {
118 assert(dest.capacity() >= dest.size() + src.size());
119 std::copy(src.begin(), src.end(), std::back_inserter(dest));
120 if constexpr (sizeof...(args) > 0)
121 combine_arg(dest, args...);
122 }
123
124 template <class Arg, class... Args>
125 static std::vector<Arg>
127 // Pass "left" by value. The values will need to be copied one way or
128 // another, so just reuse it.
129 std::vector<Arg> left,
130 std::vector<Arg> const& right,
131 Args const&... args)
132 {
133 left.reserve(totalsize(left, right, args...));
134
135 combine_arg(left, right, args...);
136
137 return left;
138 }
139
140 // All useful amendments are supported amendments.
141 // Enabled amendments are typically a subset of supported amendments.
142 // Vetoed amendments should be supported but not enabled.
143 // Unsupported amendments may be added to the AmendmentTable.
144 std::vector<std::string> const yes_{"g", "i", "k", "m", "o", "q", "r", "s", "t", "u"};
145 std::vector<std::string> const enabled_{"b", "d", "f", "h", "j", "l", "n", "p"};
146 std::vector<std::string> const vetoed_{"a", "c", "e"};
151
154
156
157public:
158 AmendmentTable_test() : journal_("AmendmentTable_test", *this)
159 {
160 }
161
164 Application& app,
165 std::chrono::seconds majorityTime,
167 Section const& enabled,
168 Section const& vetoed)
169 {
170 return make_AmendmentTable(app, majorityTime, supported, enabled, vetoed, journal_);
171 }
172
175 test::jtx::Env& env,
176 std::chrono::seconds majorityTime,
178 Section const& enabled,
179 Section const& vetoed)
180 {
181 return makeTable(env.app(), majorityTime, supported, enabled, vetoed);
182 }
183
186 {
187 static std::vector<AmendmentTable::FeatureInfo> const supported = combine(
189 // Use non-intuitive default votes for "enabled_" and "vetoed_"
190 // so that when the tests later explicitly enable or veto them,
191 // we can be certain that they are not simply going by their
192 // default vote setting.
196 return makeTable(env.app(), majorityTime, supported, makeSection(enabled_), makeSection(vetoed_));
197 }
198
199 void
201 {
202 testcase("Construction");
203 test::jtx::Env env{*this, makeConfig()};
204 auto table = makeTable(env, weeks(1));
205
206 for (auto const& a : allSupported_)
207 BEAST_EXPECT(table->isSupported(amendmentId(a)));
208
209 for (auto const& a : yes_)
210 BEAST_EXPECT(table->isSupported(amendmentId(a)));
211
212 for (auto const& a : enabled_)
213 BEAST_EXPECT(table->isSupported(amendmentId(a)));
214
215 for (auto const& a : vetoed_)
216 {
217 BEAST_EXPECT(table->isSupported(amendmentId(a)));
218 BEAST_EXPECT(!table->isEnabled(amendmentId(a)));
219 }
220
221 for (auto const& a : obsolete_)
222 {
223 BEAST_EXPECT(table->isSupported(amendmentId(a)));
224 BEAST_EXPECT(!table->isEnabled(amendmentId(a)));
225 }
226 }
227
228 void
230 {
231 testcase("Name to ID mapping");
232
233 test::jtx::Env env{*this, makeConfig()};
234 auto table = makeTable(env, weeks(1));
235
236 for (auto const& a : yes_)
237 BEAST_EXPECT(table->find(a) == amendmentId(a));
238 for (auto const& a : enabled_)
239 BEAST_EXPECT(table->find(a) == amendmentId(a));
240 for (auto const& a : vetoed_)
241 BEAST_EXPECT(table->find(a) == amendmentId(a));
242 for (auto const& a : obsolete_)
243 BEAST_EXPECT(table->find(a) == amendmentId(a));
244 for (auto const& a : unsupported_)
245 BEAST_EXPECT(!table->find(a));
246 for (auto const& a : unsupportedMajority_)
247 BEAST_EXPECT(!table->find(a));
248
249 // Vetoing an unsupported amendment should add the amendment to table.
250 // Verify that unsupportedID is not in table.
251 uint256 const unsupportedID = amendmentId(unsupported_[0]);
252 {
253 Json::Value const unsupp = table->getJson(unsupportedID, true)[to_string(unsupportedID)];
254 BEAST_EXPECT(unsupp.size() == 0);
255 }
256
257 // After vetoing unsupportedID verify that it is in table.
258 table->veto(unsupportedID);
259 {
260 Json::Value const unsupp = table->getJson(unsupportedID, true)[to_string(unsupportedID)];
261 BEAST_EXPECT(unsupp[jss::vetoed].asBool());
262 }
263 }
264
265 void
267 {
268 auto const yesVotes = makeDefaultYes(yes_);
269 auto const section = makeSection(vetoed_);
270 auto const id = to_string(amendmentId(enabled_[0]));
271
272 testcase("Bad Config");
273
274 { // Two arguments are required - we pass one
275 Section test = section;
276 test.append(id);
277
278 try
279 {
280 test::jtx::Env env{*this, makeConfig()};
281 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
282 fail("Accepted only amendment ID");
283 }
284 catch (std::exception const& e)
285 {
286 BEAST_EXPECT(e.what() == "Invalid entry '" + id + "' in [Test]");
287 }
288 }
289
290 { // Two arguments are required - we pass three
291 Section test = section;
292 test.append(id + " Test Name");
293
294 try
295 {
296 test::jtx::Env env{*this, makeConfig()};
297 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
298 fail("Accepted extra arguments");
299 }
300 catch (std::exception const& e)
301 {
302 BEAST_EXPECT(e.what() == "Invalid entry '" + id + " Test Name' in [Test]");
303 }
304 }
305
306 {
307 auto sid = id;
308 sid.resize(sid.length() - 1);
309
310 Section test = section;
311 test.append(sid + " Name");
312
313 try
314 {
315 test::jtx::Env env{*this, makeConfig()};
316 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
317 fail("Accepted short amendment ID");
318 }
319 catch (std::exception const& e)
320 {
321 BEAST_EXPECT(e.what() == "Invalid entry '" + sid + " Name' in [Test]");
322 }
323 }
324
325 {
326 auto sid = id;
327 sid.resize(sid.length() + 1, '0');
328
329 Section test = section;
330 test.append(sid + " Name");
331
332 try
333 {
334 test::jtx::Env env{*this, makeConfig()};
335 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
336 fail("Accepted long amendment ID");
337 }
338 catch (std::exception const& e)
339 {
340 BEAST_EXPECT(e.what() == "Invalid entry '" + sid + " Name' in [Test]");
341 }
342 }
343
344 {
345 auto sid = id;
346 sid.resize(sid.length() - 1);
347 sid.push_back('Q');
348
349 Section test = section;
350 test.append(sid + " Name");
351
352 try
353 {
354 test::jtx::Env env{*this, makeConfig()};
355 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
356 fail("Accepted non-hex amendment ID");
357 }
358 catch (std::exception const& e)
359 {
360 BEAST_EXPECT(e.what() == "Invalid entry '" + sid + " Name' in [Test]");
361 }
362 }
363 }
364
365 void
367 {
368 testcase("enable and veto");
369
370 test::jtx::Env env{*this, makeConfig()};
372
373 // Note which entries are enabled (convert the amendment names to IDs)
374 std::set<uint256> allEnabled;
375 for (auto const& a : enabled_)
376 allEnabled.insert(amendmentId(a));
377
378 for (uint256 const& a : allEnabled)
379 BEAST_EXPECT(table->enable(a));
380
381 // So far all enabled amendments are supported.
382 BEAST_EXPECT(!table->hasUnsupportedEnabled());
383
384 // Verify all enables are enabled and nothing else.
385 for (std::string const& a : yes_)
386 {
387 uint256 const supportedID = amendmentId(a);
388 bool const enabled = table->isEnabled(supportedID);
389 bool const found = allEnabled.find(supportedID) != allEnabled.end();
390 BEAST_EXPECTS(
391 enabled == found, a + (enabled ? " enabled " : " disabled ") + (found ? " found" : " not found"));
392 }
393
394 // All supported and unVetoed amendments should be returned as desired.
395 {
396 std::set<uint256> vetoed;
397 for (std::string const& a : vetoed_)
398 vetoed.insert(amendmentId(a));
399
400 std::vector<uint256> const desired = table->getDesired();
401 for (uint256 const& a : desired)
402 BEAST_EXPECT(vetoed.count(a) == 0);
403
404 // Unveto an amendment that is already not vetoed. Shouldn't
405 // hurt anything, but the values returned by getDesired()
406 // shouldn't change.
407 BEAST_EXPECT(!table->unVeto(amendmentId(yes_[1])));
408 BEAST_EXPECT(desired == table->getDesired());
409 }
410
411 // UnVeto one of the vetoed amendments. It should now be desired.
412 {
413 uint256 const unvetoedID = amendmentId(vetoed_[0]);
414 BEAST_EXPECT(table->unVeto(unvetoedID));
415
416 std::vector<uint256> const desired = table->getDesired();
417 BEAST_EXPECT(std::find(desired.begin(), desired.end(), unvetoedID) != desired.end());
418 }
419
420 // Veto all supported amendments. Now desired should be empty.
421 for (std::string const& a : allSupported_)
422 {
423 table->veto(amendmentId(a));
424 }
425 BEAST_EXPECT(table->getDesired().empty());
426
427 // Enable an unsupported amendment.
428 {
429 BEAST_EXPECT(!table->hasUnsupportedEnabled());
430 table->enable(amendmentId(unsupported_[0]));
431 BEAST_EXPECT(table->hasUnsupportedEnabled());
432 }
433 }
434
435 // Make a list of trusted validators.
436 // Register the validators with AmendmentTable and return the list.
439 {
441 ret.reserve(num);
442 hash_set<PublicKey> trustedValidators;
443 trustedValidators.reserve(num);
444 for (int i = 0; i < num; ++i)
445 {
446 auto const& back = ret.emplace_back(randomKeyPair(KeyType::secp256k1));
447 trustedValidators.insert(back.first);
448 }
449 table->trustChanged(trustedValidators);
450 return ret;
451 }
452
455 {
456 return NetClock::time_point{h};
457 }
458
459 // Execute a pretend consensus round for a flag ledger
460 void
462 Rules const& rules,
463 AmendmentTable& table,
467 std::vector<uint256>& ourVotes,
468 std::set<uint256>& enabled,
469 majorityAmendments_t& majority)
470 {
471 // Do a round at the specified time
472 // Returns the amendments we voted for
473
474 // Parameters:
475 // table: Our table of known and vetoed amendments
476 // validators: The addresses of validators we trust
477 // votes: Amendments and the number of validators who vote for them
478 // ourVotes: The amendments we vote for in our validation
479 // enabled: In/out enabled amendments
480 // majority: In/our majority amendments (and when they got a majority)
481
482 auto const roundTime = hourTime(hour);
483
484 // Build validations
486 validations.reserve(validators.size());
487
488 int i = 0;
489 for (auto const& [pub, sec] : validators)
490 {
491 ++i;
493
494 for (auto const& [hash, nVotes] : votes)
495 {
496 if (nVotes >= i)
497 {
498 // We vote yes on this amendment
499 field.push_back(hash);
500 }
501 }
502
504 xrpl::NetClock::time_point{}, pub, sec, calcNodeID(pub), [&field](STValidation& v) {
505 if (!field.empty())
506 v.setFieldV256(sfAmendments, STVector256(sfAmendments, field));
507 v.setFieldU32(sfLedgerSequence, 6180339);
508 });
509
510 validations.emplace_back(v);
511 }
512
513 ourVotes = table.doValidation(enabled);
514
515 auto actions = table.doVoting(rules, roundTime, enabled, majority, validations);
516 for (auto const& [hash, action] : actions)
517 {
518 // This code assumes other validators do as we do
519
520 switch (action)
521 {
522 case 0:
523 // amendment goes from majority to enabled
524 if (enabled.find(hash) != enabled.end())
525 Throw<std::runtime_error>("enabling already enabled");
526 if (majority.find(hash) == majority.end())
527 Throw<std::runtime_error>("enabling without majority");
528 enabled.insert(hash);
529 majority.erase(hash);
530 break;
531
532 case tfGotMajority:
533 if (majority.find(hash) != majority.end())
534 Throw<std::runtime_error>("got majority while having majority");
535 majority[hash] = roundTime;
536 break;
537
538 case tfLostMajority:
539 if (majority.find(hash) == majority.end())
540 Throw<std::runtime_error>("lost majority without majority");
541 majority.erase(hash);
542 break;
543
544 default:
545 Throw<std::runtime_error>("unknown action");
546 }
547 }
548 }
549
550 // No vote on unknown amendment
551 void
553 {
554 testcase("Vote NO on unknown");
555
556 auto const testAmendment = amendmentId("TestAmendment");
557
558 test::jtx::Env env{*this, feat};
559 auto table = makeTable(env, weeks(2), emptyYes_, emptySection_, emptySection_);
560
561 auto const validators = makeValidators(10, table);
562
564 std::vector<uint256> ourVotes;
565 std::set<uint256> enabled;
566 majorityAmendments_t majority;
567
568 doRound(env.current()->rules(), *table, weeks{1}, validators, votes, ourVotes, enabled, majority);
569 BEAST_EXPECT(ourVotes.empty());
570 BEAST_EXPECT(enabled.empty());
571 BEAST_EXPECT(majority.empty());
572
573 uint256 const unsupportedID = amendmentId(unsupported_[0]);
574 {
575 Json::Value const unsupp = table->getJson(unsupportedID, false)[to_string(unsupportedID)];
576 BEAST_EXPECT(unsupp.size() == 0);
577 }
578
579 table->veto(unsupportedID);
580 {
581 Json::Value const unsupp = table->getJson(unsupportedID, false)[to_string(unsupportedID)];
582 BEAST_EXPECT(!unsupp[jss::vetoed].asBool());
583 }
584
585 votes.emplace_back(testAmendment, validators.size());
586
587 votes.emplace_back(testAmendment, validators.size());
588
589 doRound(env.current()->rules(), *table, weeks{2}, validators, votes, ourVotes, enabled, majority);
590 BEAST_EXPECT(ourVotes.empty());
591 BEAST_EXPECT(enabled.empty());
592
593 majority[testAmendment] = hourTime(weeks{1});
594
595 // Note that the simulation code assumes others behave as we do,
596 // so the amendment won't get enabled
597 doRound(env.current()->rules(), *table, weeks{5}, validators, votes, ourVotes, enabled, majority);
598 BEAST_EXPECT(ourVotes.empty());
599 BEAST_EXPECT(enabled.empty());
600 }
601
602 // No vote on vetoed amendment
603 void
605 {
606 testcase("Vote NO on vetoed");
607
608 auto const testAmendment = amendmentId("vetoedAmendment");
609
610 test::jtx::Env env{*this, feat};
611 auto table = makeTable(env, weeks(2), emptyYes_, emptySection_, makeSection(testAmendment));
612
613 auto const validators = makeValidators(10, table);
614
616 std::vector<uint256> ourVotes;
617 std::set<uint256> enabled;
618 majorityAmendments_t majority;
619
620 doRound(env.current()->rules(), *table, weeks{1}, validators, votes, ourVotes, enabled, majority);
621 BEAST_EXPECT(ourVotes.empty());
622 BEAST_EXPECT(enabled.empty());
623 BEAST_EXPECT(majority.empty());
624
625 votes.emplace_back(testAmendment, validators.size());
626
627 doRound(env.current()->rules(), *table, weeks{2}, validators, votes, ourVotes, enabled, majority);
628 BEAST_EXPECT(ourVotes.empty());
629 BEAST_EXPECT(enabled.empty());
630
631 majority[testAmendment] = hourTime(weeks{1});
632
633 doRound(env.current()->rules(), *table, weeks{5}, validators, votes, ourVotes, enabled, majority);
634 BEAST_EXPECT(ourVotes.empty());
635 BEAST_EXPECT(enabled.empty());
636 }
637
638 // Vote on and enable known, not-enabled amendment
639 void
641 {
642 testcase("voteEnable");
643
644 test::jtx::Env env{*this, feat};
646
647 auto const validators = makeValidators(10, table);
648
650 std::vector<uint256> ourVotes;
651 std::set<uint256> enabled;
652 majorityAmendments_t majority;
653
654 // Week 1: We should vote for all known amendments not enabled
655 doRound(env.current()->rules(), *table, weeks{1}, validators, votes, ourVotes, enabled, majority);
656 BEAST_EXPECT(ourVotes.size() == yes_.size());
657 BEAST_EXPECT(enabled.empty());
658 for (auto const& i : yes_)
659 BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
660
661 // Now, everyone votes for this feature
662 for (auto const& i : yes_)
663 votes.emplace_back(amendmentId(i), validators.size());
664
665 // Week 2: We should recognize a majority
666 doRound(env.current()->rules(), *table, weeks{2}, validators, votes, ourVotes, enabled, majority);
667 BEAST_EXPECT(ourVotes.size() == yes_.size());
668 BEAST_EXPECT(enabled.empty());
669
670 for (auto const& i : yes_)
671 BEAST_EXPECT(majority[amendmentId(i)] == hourTime(weeks{2}));
672
673 // Week 5: We should enable the amendment
674 doRound(env.current()->rules(), *table, weeks{5}, validators, votes, ourVotes, enabled, majority);
675 BEAST_EXPECT(enabled.size() == yes_.size());
676
677 // Week 6: We should remove it from our votes and from having a majority
678 doRound(env.current()->rules(), *table, weeks{6}, validators, votes, ourVotes, enabled, majority);
679 BEAST_EXPECT(enabled.size() == yes_.size());
680 BEAST_EXPECT(ourVotes.empty());
681 for (auto const& i : yes_)
682 BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
683 }
684
685 // Detect majority at 80%, enable later
686 void
688 {
689 testcase("detectMajority");
690
691 auto const testAmendment = amendmentId("detectMajority");
692 test::jtx::Env env{*this, feat};
693 auto table = makeTable(env, weeks(2), makeDefaultYes(testAmendment), emptySection_, emptySection_);
694
695 auto const validators = makeValidators(16, table);
696
697 std::set<uint256> enabled;
698 majorityAmendments_t majority;
699
700 for (int i = 0; i <= 17; ++i)
701 {
703 std::vector<uint256> ourVotes;
704
705 if ((i > 0) && (i < 17))
706 votes.emplace_back(testAmendment, i);
707
708 doRound(env.current()->rules(), *table, weeks{i}, validators, votes, ourVotes, enabled, majority);
709
710 if (i < 13) // 13 => 13/16 = 0.8125 => > 80%
711 {
712 // We are voting yes, not enabled, no majority
713 BEAST_EXPECT(!ourVotes.empty());
714 BEAST_EXPECT(enabled.empty());
715 BEAST_EXPECT(majority.empty());
716 }
717 else if (i < 15)
718 {
719 // We have a majority, not enabled, keep voting
720 BEAST_EXPECT(!ourVotes.empty());
721 BEAST_EXPECT(!majority.empty());
722 BEAST_EXPECT(enabled.empty());
723 }
724 else if (i == 15)
725 {
726 // enable, keep voting, remove from majority
727 BEAST_EXPECT(!ourVotes.empty());
728 BEAST_EXPECT(majority.empty());
729 BEAST_EXPECT(!enabled.empty());
730 }
731 else
732 {
733 // Done, we should be enabled and not voting
734 BEAST_EXPECT(ourVotes.empty());
735 BEAST_EXPECT(majority.empty());
736 BEAST_EXPECT(!enabled.empty());
737 }
738 }
739 }
740
741 // Detect loss of majority
742 void
744 {
745 testcase("lostMajority");
746
747 auto const testAmendment = amendmentId("lostMajority");
748
749 test::jtx::Env env{*this, feat};
750 auto table = makeTable(env, weeks(8), makeDefaultYes(testAmendment), emptySection_, emptySection_);
751
752 auto const validators = makeValidators(16, table);
753
754 std::set<uint256> enabled;
755 majorityAmendments_t majority;
756
757 {
758 // establish majority
760 std::vector<uint256> ourVotes;
761
762 votes.emplace_back(testAmendment, validators.size());
763
764 doRound(env.current()->rules(), *table, weeks{1}, validators, votes, ourVotes, enabled, majority);
765
766 BEAST_EXPECT(enabled.empty());
767 BEAST_EXPECT(!majority.empty());
768 }
769
770 for (int i = 1; i < 8; ++i)
771 {
773 std::vector<uint256> ourVotes;
774
775 // Gradually reduce support
776 votes.emplace_back(testAmendment, validators.size() - i);
777
778 doRound(env.current()->rules(), *table, weeks{i + 1}, validators, votes, ourVotes, enabled, majority);
779
780 if (i < 4) // 16 - 3 = 13 => 13/16 = 0.8125 => > 80%
781 { // 16 - 4 = 12 => 12/16 = 0.75 => < 80%
782 // We are voting yes, not enabled, majority
783 BEAST_EXPECT(!ourVotes.empty());
784 BEAST_EXPECT(enabled.empty());
785 BEAST_EXPECT(!majority.empty());
786 }
787 else
788 {
789 // No majority, not enabled, keep voting
790 BEAST_EXPECT(!ourVotes.empty());
791 BEAST_EXPECT(majority.empty());
792 BEAST_EXPECT(enabled.empty());
793 }
794 }
795 }
796
797 // Exercise the UNL changing while voting is in progress.
798 void
800 {
801 testcase("changedUNL");
802
803 auto const testAmendment = amendmentId("changedUNL");
804 test::jtx::Env env{*this, feat};
805 auto table = makeTable(env, weeks(8), makeDefaultYes(testAmendment), emptySection_, emptySection_);
806
808
809 std::set<uint256> enabled;
810 majorityAmendments_t majority;
811
812 {
813 // 10 validators with 2 voting against won't get majority.
815 std::vector<uint256> ourVotes;
816
817 votes.emplace_back(testAmendment, validators.size() - 2);
818
819 doRound(env.current()->rules(), *table, weeks{1}, validators, votes, ourVotes, enabled, majority);
820
821 BEAST_EXPECT(enabled.empty());
822 BEAST_EXPECT(majority.empty());
823 }
824
825 // Add one new validator to the UNL.
827
828 // A lambda that updates the AmendmentTable with the latest
829 // trusted validators.
830 auto callTrustChanged = [](std::vector<std::pair<PublicKey, SecretKey>> const& validators,
831 std::unique_ptr<AmendmentTable> const& table) {
832 // We need a hash_set to pass to trustChanged.
833 hash_set<PublicKey> trustedValidators;
834 trustedValidators.reserve(validators.size());
835 std::for_each(validators.begin(), validators.end(), [&trustedValidators](auto const& val) {
836 trustedValidators.insert(val.first);
837 });
838
839 // Tell the AmendmentTable that the UNL changed.
840 table->trustChanged(trustedValidators);
841 };
842
843 // Tell the table that there's been a change in trusted validators.
844 callTrustChanged(validators, table);
845
846 {
847 // 11 validators with 2 voting against gains majority.
849 std::vector<uint256> ourVotes;
850
851 votes.emplace_back(testAmendment, validators.size() - 2);
852
853 doRound(env.current()->rules(), *table, weeks{2}, validators, votes, ourVotes, enabled, majority);
854
855 BEAST_EXPECT(enabled.empty());
856 BEAST_EXPECT(!majority.empty());
857 }
858 {
859 // One of the validators goes flaky and doesn't send validations
860 // (without the UNL changing) so the amendment loses majority.
861 std::pair<PublicKey, SecretKey> const savedValidator = validators.front();
862 validators.erase(validators.begin());
863
865 std::vector<uint256> ourVotes;
866
867 votes.emplace_back(testAmendment, validators.size() - 2);
868
869 doRound(env.current()->rules(), *table, weeks{3}, validators, votes, ourVotes, enabled, majority);
870
871 BEAST_EXPECT(enabled.empty());
872 BEAST_EXPECT(majority.empty());
873
874 // Simulate the validator re-syncing to the network by adding it
875 // back to the validators vector
876 validators.insert(validators.begin(), savedValidator);
877
878 votes.front().second = validators.size() - 2;
879
880 doRound(env.current()->rules(), *table, weeks{4}, validators, votes, ourVotes, enabled, majority);
881
882 BEAST_EXPECT(enabled.empty());
883 BEAST_EXPECT(!majority.empty());
884
885 // Finally, remove one validator from the UNL and see that majority
886 // is lost.
887 validators.erase(validators.begin());
888
889 // Tell the table that there's been a change in trusted validators.
890 callTrustChanged(validators, table);
891
892 votes.front().second = validators.size() - 2;
893
894 doRound(env.current()->rules(), *table, weeks{5}, validators, votes, ourVotes, enabled, majority);
895
896 BEAST_EXPECT(enabled.empty());
897 BEAST_EXPECT(majority.empty());
898 }
899 }
900
901 // Exercise a validator losing connectivity and then regaining it after
902 // extended delays. Depending on how long that delay is an amendment
903 // either will or will not go live.
904 void
906 {
907 testcase("validatorFlapping");
908
909 // We run a test where a validator flaps on and off every 23 hours
910 // and another one one where it flaps on and off every 25 hours.
911 //
912 // Since the local validator vote record expires after 24 hours,
913 // with 23 hour flapping the amendment will go live. But with 25
914 // hour flapping the amendment will not go live.
915 for (int flapRateHours : {23, 25})
916 {
917 test::jtx::Env env{*this, feat};
918 auto const testAmendment = amendmentId("validatorFlapping");
919 auto table = makeTable(env, weeks(1), makeDefaultYes(testAmendment), emptySection_, emptySection_);
920
921 // Make two lists of validators, one with a missing validator, to
922 // make it easy to simulate validator flapping.
923 auto const allValidators = makeValidators(11, table);
924 decltype(allValidators) const mostValidators(allValidators.begin() + 1, allValidators.end());
925 BEAST_EXPECT(allValidators.size() == mostValidators.size() + 1);
926
927 std::set<uint256> enabled;
928 majorityAmendments_t majority;
929
931 std::vector<uint256> ourVotes;
932
933 votes.emplace_back(testAmendment, allValidators.size() - 2);
934
935 int delay = flapRateHours;
936 // Loop for 1 week plus a day.
937 for (int hour = 1; hour < (24 * 8); ++hour)
938 {
939 decltype(allValidators) const& thisHoursValidators =
940 (delay < flapRateHours) ? mostValidators : allValidators;
941 delay = delay == flapRateHours ? 0 : delay + 1;
942
943 votes.front().second = thisHoursValidators.size() - 2;
944
945 using namespace std::chrono;
946 doRound(
947 env.current()->rules(),
948 *table,
949 hours(hour),
950 thisHoursValidators,
951 votes,
952 ourVotes,
953 enabled,
954 majority);
955
956 if (hour <= (24 * 7) || flapRateHours > 24)
957 {
958 // The amendment should not be enabled under any
959 // circumstance until one week has elapsed.
960 BEAST_EXPECT(enabled.empty());
961
962 // If flapping is less than 24 hours, there should be
963 // no flapping. Otherwise we should only have majority
964 // if allValidators vote -- which means there are no
965 // missing validators.
966 bool const expectMajority = (delay <= 24) ? true : &thisHoursValidators == &allValidators;
967 BEAST_EXPECT(majority.empty() != expectMajority);
968 }
969 else
970 {
971 // We're...
972 // o Past one week, and
973 // o AmendmentFlapping was less than 24 hours.
974 // The amendment should be enabled.
975 BEAST_EXPECT(!enabled.empty());
976 BEAST_EXPECT(majority.empty());
977 }
978 }
979 }
980 }
981
982 void
984 {
985 testcase("hasUnsupportedEnabled");
986
987 using namespace std::chrono_literals;
988 weeks constexpr w(1);
989 test::jtx::Env env{*this, makeConfig()};
990 auto table = makeTable(env, w);
991 BEAST_EXPECT(!table->hasUnsupportedEnabled());
992 BEAST_EXPECT(!table->firstUnsupportedExpected());
993 BEAST_EXPECT(table->needValidatedLedger(1));
994
995 std::set<uint256> enabled;
997 unsupported_.begin(), unsupported_.end(), [&enabled](auto const& s) { enabled.insert(amendmentId(s)); });
998
999 majorityAmendments_t majority;
1000 table->doValidatedLedger(1, enabled, majority);
1001 BEAST_EXPECT(table->hasUnsupportedEnabled());
1002 BEAST_EXPECT(!table->firstUnsupportedExpected());
1003
1004 NetClock::duration t{1000s};
1005 std::for_each(unsupportedMajority_.begin(), unsupportedMajority_.end(), [&majority, &t](auto const& s) {
1006 majority[amendmentId(s)] = NetClock::time_point{--t};
1007 });
1008
1009 table->doValidatedLedger(1, enabled, majority);
1010 BEAST_EXPECT(table->hasUnsupportedEnabled());
1011 BEAST_EXPECT(
1012 table->firstUnsupportedExpected() && *table->firstUnsupportedExpected() == NetClock::time_point{t} + w);
1013
1014 // Make sure the table knows when it needs an update.
1015 BEAST_EXPECT(!table->needValidatedLedger(256));
1016 BEAST_EXPECT(table->needValidatedLedger(257));
1017 }
1018
1019 void
1021 {
1022 testNoOnUnknown(feat);
1023 testNoOnVetoed(feat);
1024 testVoteEnable(feat);
1025 testDetectMajority(feat);
1026 testLostMajority(feat);
1027 testChangedUNL(feat);
1028 testValidatorFlapping(feat);
1029 }
1030
1031 void
1032 run() override
1033 {
1035
1036 testConstruct();
1037 testGet();
1038 testBadConfig();
1039 testEnableVeto();
1040 testHasUnsupported();
1041 testFeature(all);
1042 }
1043};
1044
1045BEAST_DEFINE_TESTSUITE(AmendmentTable, app, xrpl);
1046
1047} // namespace xrpl
T back_inserter(T... args)
T begin(T... args)
T capacity(T... args)
Represents a JSON value.
Definition json_value.h:131
UInt size() const
Number of values in array or object.
A testsuite class.
Definition suite.h:52
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
static Section makeSection(std::string const &name, std::vector< std::string > const &amendments)
void run() override
Runs the suite.
static NetClock::time_point hourTime(std::chrono::hours h)
void testLostMajority(FeatureBitset const &feat)
static std::vector< AmendmentTable::FeatureInfo > makeFeatureInfo(std::vector< std::string > const &amendments, VoteBehavior voteBehavior)
std::unique_ptr< AmendmentTable > makeTable(test::jtx::Env &env, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed)
static std::vector< AmendmentTable::FeatureInfo > makeDefaultYes(std::vector< std::string > const &amendments)
void testDetectMajority(FeatureBitset const &feat)
void testValidatorFlapping(FeatureBitset const &feat)
std::vector< AmendmentTable::FeatureInfo > const emptyYes_
std::vector< std::pair< PublicKey, SecretKey > > makeValidators(int num, std::unique_ptr< AmendmentTable > const &table)
void testChangedUNL(FeatureBitset const &feat)
static Section makeSection(std::vector< std::string > const &amendments)
std::vector< std::string > const unsupportedMajority_
void testNoOnVetoed(FeatureBitset const &feat)
std::unique_ptr< AmendmentTable > makeTable(Application &app, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed)
static Section makeSection(uint256 const &amendment)
static void combine_arg(std::vector< Arg > &dest, std::vector< Arg > const &src, Args const &... args)
static std::vector< AmendmentTable::FeatureInfo > makeObsolete(std::vector< std::string > const &amendments)
static std::vector< Arg > combine(std::vector< Arg > left, std::vector< Arg > const &right, Args const &... args)
std::vector< std::string > const vetoed_
std::unique_ptr< AmendmentTable > makeTable(test::jtx::Env &env, std::chrono::seconds majorityTime)
static std::vector< AmendmentTable::FeatureInfo > makeDefaultYes(uint256 const amendment)
std::vector< std::string > const enabled_
static uint256 amendmentId(std::string in)
static size_t totalsize(std::vector< Arg > const &src, Args const &... args)
std::vector< std::string > const allSupported_
std::vector< std::string > const unsupported_
std::vector< std::string > const obsolete_
void testVoteEnable(FeatureBitset const &feat)
static std::vector< AmendmentTable::FeatureInfo > makeDefaultNo(std::vector< std::string > const &amendments)
std::unique_ptr< Config > makeConfig()
void doRound(Rules const &rules, AmendmentTable &table, std::chrono::hours hour, std::vector< std::pair< PublicKey, SecretKey > > const &validators, std::vector< std::pair< uint256, int > > const &votes, std::vector< uint256 > &ourVotes, std::set< uint256 > &enabled, majorityAmendments_t &majority)
std::vector< std::string > const yes_
void testFeature(FeatureBitset const &feat)
void testNoOnUnknown(FeatureBitset const &feat)
The amendment table stores the list of enabled and potential amendments.
virtual std::vector< uint256 > doValidation(std::set< uint256 > const &enabled) const =0
virtual std::map< uint256, std::uint32_t > doVoting(Rules const &rules, NetClock::time_point closeTime, std::set< uint256 > const &enabledAmendments, majorityAmendments_t const &majorityAmendments, std::vector< std::shared_ptr< STValidation > > const &valSet)=0
Rules controlling protocol behavior.
Definition Rules.h:19
Holds a collection of configuration values.
Definition BasicConfig.h:25
void append(std::vector< std::string > const &lines)
Append a set of lines to this section.
pointer data()
Definition base_uint.h:102
static constexpr std::size_t size()
Definition base_uint.h:495
A transaction testing environment.
Definition Env.h:98
Application & app()
Definition Env.h:230
T copy(T... args)
T count(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T for_each(T... args)
T front(T... args)
T insert(T... args)
T is_same_v
T memcpy(T... args)
std::enable_if_t< is_contiguously_hashable< T, Hasher >::value > hash_append(Hasher &h, T const &t) noexcept
Logically concatenate input data to a Hasher.
FeatureBitset testable_amendments()
Definition Env.h:55
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
VoteBehavior
Definition Feature.h:68
std::chrono::duration< int, std::ratio_multiply< days::period, std::ratio< 7 > > > weeks
Definition chrono.h:21
std::unique_ptr< AmendmentTable > make_AmendmentTable(Application &app, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
constexpr std::uint32_t tfLostMajority
Definition TxFlags.h:110
constexpr std::uint32_t tfGotMajority
Definition TxFlags.h:109
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
void hash_append(Hasher &h, Slice const &v)
Definition Slice.h:175
T reserve(T... args)
T size(T... args)
SHA-256 digest.
Definition digest.h:75
T what(T... args)