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 ripple {
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
38 std::string const& name,
39 std::vector<std::string> const& amendments)
40 {
41 Section section(name);
42 for (auto const& a : amendments)
43 section.append(to_string(amendmentId(a)) + " " + a);
44 return section;
45 }
46
47 static Section
49 {
50 return makeSection("Test", amendments);
51 }
52
53 static Section
54 makeSection(uint256 const& amendment)
55 {
56 Section section("Test");
57 section.append(to_string(amendment) + " " + to_string(amendment));
58 return section;
59 }
60
63 {
64 auto cfg = test::jtx::envconfig();
65 cfg->section(SECTION_AMENDMENTS) =
66 makeSection(SECTION_AMENDMENTS, enabled_);
67 cfg->section(SECTION_VETO_AMENDMENTS) =
68 makeSection(SECTION_VETO_AMENDMENTS, vetoed_);
69 return cfg;
70 }
71
74 std::vector<std::string> const& amendments,
75 VoteBehavior voteBehavior)
76 {
78 result.reserve(amendments.size());
79 for (auto const& a : amendments)
80 {
81 result.emplace_back(a, amendmentId(a), voteBehavior);
82 }
83 return result;
84 }
85
88 {
89 return makeFeatureInfo(amendments, VoteBehavior::DefaultYes);
90 }
91
93 makeDefaultYes(uint256 const amendment)
94 {
96 {to_string(amendment), amendment, VoteBehavior::DefaultYes}};
97 return result;
98 }
99
102 {
103 return makeFeatureInfo(amendments, VoteBehavior::DefaultNo);
104 }
105
108 {
109 return makeFeatureInfo(amendments, VoteBehavior::Obsolete);
110 }
111
112 template <class Arg, class... Args>
113 static size_t
114 totalsize(std::vector<Arg> const& src, Args const&... args)
115 {
116 if constexpr (sizeof...(args) > 0)
117 return src.size() + totalsize(args...);
118 return src.size();
119 }
120
121 template <class Arg, class... Args>
122 static void
124 std::vector<Arg>& dest,
125 std::vector<Arg> const& src,
126 Args const&... args)
127 {
128 assert(dest.capacity() >= dest.size() + src.size());
129 std::copy(src.begin(), src.end(), std::back_inserter(dest));
130 if constexpr (sizeof...(args) > 0)
131 combine_arg(dest, args...);
132 }
133
134 template <class Arg, class... Args>
135 static std::vector<Arg>
137 // Pass "left" by value. The values will need to be copied one way or
138 // another, so just reuse it.
139 std::vector<Arg> left,
140 std::vector<Arg> const& right,
141 Args const&... args)
142 {
143 left.reserve(totalsize(left, right, args...));
144
145 combine_arg(left, right, args...);
146
147 return left;
148 }
149
150 // All useful amendments are supported amendments.
151 // Enabled amendments are typically a subset of supported amendments.
152 // Vetoed amendments should be supported but not enabled.
153 // Unsupported amendments may be added to the AmendmentTable.
155 yes_{"g", "i", "k", "m", "o", "q", "r", "s", "t", "u"};
157 enabled_{"b", "d", "f", "h", "j", "l", "n", "p"};
158 std::vector<std::string> const vetoed_{"a", "c", "e"};
164
167
169
170public:
171 AmendmentTable_test() : journal_("AmendmentTable_test", *this)
172 {
173 }
174
177 Application& app,
178 std::chrono::seconds majorityTime,
180 Section const& enabled,
181 Section const& vetoed)
182 {
183 return make_AmendmentTable(
184 app, majorityTime, supported, enabled, vetoed, journal_);
185 }
186
189 test::jtx::Env& env,
190 std::chrono::seconds majorityTime,
192 Section const& enabled,
193 Section const& vetoed)
194 {
195 return makeTable(env.app(), majorityTime, supported, enabled, vetoed);
196 }
197
200 {
201 static std::vector<AmendmentTable::FeatureInfo> const supported =
202 combine(
204 // Use non-intuitive default votes for "enabled_" and "vetoed_"
205 // so that when the tests later explicitly enable or veto them,
206 // we can be certain that they are not simply going by their
207 // default vote setting.
211 return makeTable(
212 env.app(),
213 majorityTime,
214 supported,
217 }
218
219 void
221 {
222 testcase("Construction");
223 test::jtx::Env env{*this, makeConfig()};
224 auto table = makeTable(env, weeks(1));
225
226 for (auto const& a : allSupported_)
227 BEAST_EXPECT(table->isSupported(amendmentId(a)));
228
229 for (auto const& a : yes_)
230 BEAST_EXPECT(table->isSupported(amendmentId(a)));
231
232 for (auto const& a : enabled_)
233 BEAST_EXPECT(table->isSupported(amendmentId(a)));
234
235 for (auto const& a : vetoed_)
236 {
237 BEAST_EXPECT(table->isSupported(amendmentId(a)));
238 BEAST_EXPECT(!table->isEnabled(amendmentId(a)));
239 }
240
241 for (auto const& a : obsolete_)
242 {
243 BEAST_EXPECT(table->isSupported(amendmentId(a)));
244 BEAST_EXPECT(!table->isEnabled(amendmentId(a)));
245 }
246 }
247
248 void
250 {
251 testcase("Name to ID mapping");
252
253 test::jtx::Env env{*this, makeConfig()};
254 auto table = makeTable(env, weeks(1));
255
256 for (auto const& a : yes_)
257 BEAST_EXPECT(table->find(a) == amendmentId(a));
258 for (auto const& a : enabled_)
259 BEAST_EXPECT(table->find(a) == amendmentId(a));
260 for (auto const& a : vetoed_)
261 BEAST_EXPECT(table->find(a) == amendmentId(a));
262 for (auto const& a : obsolete_)
263 BEAST_EXPECT(table->find(a) == amendmentId(a));
264 for (auto const& a : unsupported_)
265 BEAST_EXPECT(!table->find(a));
266 for (auto const& a : unsupportedMajority_)
267 BEAST_EXPECT(!table->find(a));
268
269 // Vetoing an unsupported amendment should add the amendment to table.
270 // Verify that unsupportedID is not in table.
271 uint256 const unsupportedID = amendmentId(unsupported_[0]);
272 {
273 Json::Value const unsupp =
274 table->getJson(unsupportedID, true)[to_string(unsupportedID)];
275 BEAST_EXPECT(unsupp.size() == 0);
276 }
277
278 // After vetoing unsupportedID verify that it is in table.
279 table->veto(unsupportedID);
280 {
281 Json::Value const unsupp =
282 table->getJson(unsupportedID, true)[to_string(unsupportedID)];
283 BEAST_EXPECT(unsupp[jss::vetoed].asBool());
284 }
285 }
286
287 void
289 {
290 auto const yesVotes = makeDefaultYes(yes_);
291 auto const section = makeSection(vetoed_);
292 auto const id = to_string(amendmentId(enabled_[0]));
293
294 testcase("Bad Config");
295
296 { // Two arguments are required - we pass one
297 Section test = section;
298 test.append(id);
299
300 try
301 {
302 test::jtx::Env env{*this, makeConfig()};
303 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
304 fail("Accepted only amendment ID");
305 }
306 catch (std::exception const& e)
307 {
308 BEAST_EXPECT(
309 e.what() == "Invalid entry '" + id + "' in [Test]");
310 }
311 }
312
313 { // Two arguments are required - we pass three
314 Section test = section;
315 test.append(id + " Test Name");
316
317 try
318 {
319 test::jtx::Env env{*this, makeConfig()};
320 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
321 fail("Accepted extra arguments");
322 }
323 catch (std::exception const& e)
324 {
325 BEAST_EXPECT(
326 e.what() ==
327 "Invalid entry '" + id + " Test Name' in [Test]");
328 }
329 }
330
331 {
332 auto sid = id;
333 sid.resize(sid.length() - 1);
334
335 Section test = section;
336 test.append(sid + " Name");
337
338 try
339 {
340 test::jtx::Env env{*this, makeConfig()};
341 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
342 fail("Accepted short amendment ID");
343 }
344 catch (std::exception const& e)
345 {
346 BEAST_EXPECT(
347 e.what() == "Invalid entry '" + sid + " Name' in [Test]");
348 }
349 }
350
351 {
352 auto sid = id;
353 sid.resize(sid.length() + 1, '0');
354
355 Section test = section;
356 test.append(sid + " Name");
357
358 try
359 {
360 test::jtx::Env env{*this, makeConfig()};
361 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
362 fail("Accepted long amendment ID");
363 }
364 catch (std::exception const& e)
365 {
366 BEAST_EXPECT(
367 e.what() == "Invalid entry '" + sid + " Name' in [Test]");
368 }
369 }
370
371 {
372 auto sid = id;
373 sid.resize(sid.length() - 1);
374 sid.push_back('Q');
375
376 Section test = section;
377 test.append(sid + " Name");
378
379 try
380 {
381 test::jtx::Env env{*this, makeConfig()};
382 if (makeTable(env, weeks(2), yesVotes, test, emptySection_))
383 fail("Accepted non-hex amendment ID");
384 }
385 catch (std::exception const& e)
386 {
387 BEAST_EXPECT(
388 e.what() == "Invalid entry '" + sid + " Name' in [Test]");
389 }
390 }
391 }
392
393 void
395 {
396 testcase("enable and veto");
397
398 test::jtx::Env env{*this, makeConfig()};
400
401 // Note which entries are enabled (convert the amendment names to IDs)
402 std::set<uint256> allEnabled;
403 for (auto const& a : enabled_)
404 allEnabled.insert(amendmentId(a));
405
406 for (uint256 const& a : allEnabled)
407 BEAST_EXPECT(table->enable(a));
408
409 // So far all enabled amendments are supported.
410 BEAST_EXPECT(!table->hasUnsupportedEnabled());
411
412 // Verify all enables are enabled and nothing else.
413 for (std::string const& a : yes_)
414 {
415 uint256 const supportedID = amendmentId(a);
416 bool const enabled = table->isEnabled(supportedID);
417 bool const found = allEnabled.find(supportedID) != allEnabled.end();
418 BEAST_EXPECTS(
419 enabled == found,
420 a + (enabled ? " enabled " : " disabled ") +
421 (found ? " found" : " not found"));
422 }
423
424 // All supported and unVetoed amendments should be returned as desired.
425 {
426 std::set<uint256> vetoed;
427 for (std::string const& a : vetoed_)
428 vetoed.insert(amendmentId(a));
429
430 std::vector<uint256> const desired = table->getDesired();
431 for (uint256 const& a : desired)
432 BEAST_EXPECT(vetoed.count(a) == 0);
433
434 // Unveto an amendment that is already not vetoed. Shouldn't
435 // hurt anything, but the values returned by getDesired()
436 // shouldn't change.
437 BEAST_EXPECT(!table->unVeto(amendmentId(yes_[1])));
438 BEAST_EXPECT(desired == table->getDesired());
439 }
440
441 // UnVeto one of the vetoed amendments. It should now be desired.
442 {
443 uint256 const unvetoedID = amendmentId(vetoed_[0]);
444 BEAST_EXPECT(table->unVeto(unvetoedID));
445
446 std::vector<uint256> const desired = table->getDesired();
447 BEAST_EXPECT(
448 std::find(desired.begin(), desired.end(), unvetoedID) !=
449 desired.end());
450 }
451
452 // Veto all supported amendments. Now desired should be empty.
453 for (std::string const& a : allSupported_)
454 {
455 table->veto(amendmentId(a));
456 }
457 BEAST_EXPECT(table->getDesired().empty());
458
459 // Enable an unsupported amendment.
460 {
461 BEAST_EXPECT(!table->hasUnsupportedEnabled());
462 table->enable(amendmentId(unsupported_[0]));
463 BEAST_EXPECT(table->hasUnsupportedEnabled());
464 }
465 }
466
467 // Make a list of trusted validators.
468 // Register the validators with AmendmentTable and return the list.
471 {
473 ret.reserve(num);
474 hash_set<PublicKey> trustedValidators;
475 trustedValidators.reserve(num);
476 for (int i = 0; i < num; ++i)
477 {
478 auto const& back =
480 trustedValidators.insert(back.first);
481 }
482 table->trustChanged(trustedValidators);
483 return ret;
484 }
485
488 {
489 return NetClock::time_point{h};
490 }
491
492 // Execute a pretend consensus round for a flag ledger
493 void
495 Rules const& rules,
496 AmendmentTable& table,
500 std::vector<uint256>& ourVotes,
501 std::set<uint256>& enabled,
502 majorityAmendments_t& majority)
503 {
504 // Do a round at the specified time
505 // Returns the amendments we voted for
506
507 // Parameters:
508 // table: Our table of known and vetoed amendments
509 // validators: The addreses of validators we trust
510 // votes: Amendments and the number of validators who vote for them
511 // ourVotes: The amendments we vote for in our validation
512 // enabled: In/out enabled amendments
513 // majority: In/our majority amendments (and when they got a majority)
514
515 auto const roundTime = hourTime(hour);
516
517 // Build validations
519 validations.reserve(validators.size());
520
521 int i = 0;
522 for (auto const& [pub, sec] : validators)
523 {
524 ++i;
526
527 for (auto const& [hash, nVotes] : votes)
528 {
529 if (nVotes >= i)
530 {
531 // We vote yes on this amendment
532 field.push_back(hash);
533 }
534 }
535
538 pub,
539 sec,
540 calcNodeID(pub),
541 [&field](STValidation& v) {
542 if (!field.empty())
543 v.setFieldV256(
544 sfAmendments, STVector256(sfAmendments, field));
545 v.setFieldU32(sfLedgerSequence, 6180339);
546 });
547
548 validations.emplace_back(v);
549 }
550
551 ourVotes = table.doValidation(enabled);
552
553 auto actions =
554 table.doVoting(rules, roundTime, enabled, majority, validations);
555 for (auto const& [hash, action] : actions)
556 {
557 // This code assumes other validators do as we do
558
559 switch (action)
560 {
561 case 0:
562 // amendment goes from majority to enabled
563 if (enabled.find(hash) != enabled.end())
564 Throw<std::runtime_error>("enabling already enabled");
565 if (majority.find(hash) == majority.end())
566 Throw<std::runtime_error>("enabling without majority");
567 enabled.insert(hash);
568 majority.erase(hash);
569 break;
570
571 case tfGotMajority:
572 if (majority.find(hash) != majority.end())
573 Throw<std::runtime_error>(
574 "got majority while having majority");
575 majority[hash] = roundTime;
576 break;
577
578 case tfLostMajority:
579 if (majority.find(hash) == majority.end())
580 Throw<std::runtime_error>(
581 "lost majority without majority");
582 majority.erase(hash);
583 break;
584
585 default:
586 Throw<std::runtime_error>("unknown action");
587 }
588 }
589 }
590
591 // No vote on unknown amendment
592 void
594 {
595 testcase("Vote NO on unknown");
596
597 auto const testAmendment = amendmentId("TestAmendment");
598
599 test::jtx::Env env{*this, feat};
600 auto table =
602
603 auto const validators = makeValidators(10, table);
604
606 std::vector<uint256> ourVotes;
607 std::set<uint256> enabled;
608 majorityAmendments_t majority;
609
610 doRound(
611 env.current()->rules(),
612 *table,
613 weeks{1},
614 validators,
615 votes,
616 ourVotes,
617 enabled,
618 majority);
619 BEAST_EXPECT(ourVotes.empty());
620 BEAST_EXPECT(enabled.empty());
621 BEAST_EXPECT(majority.empty());
622
623 uint256 const unsupportedID = amendmentId(unsupported_[0]);
624 {
625 Json::Value const unsupp =
626 table->getJson(unsupportedID, false)[to_string(unsupportedID)];
627 BEAST_EXPECT(unsupp.size() == 0);
628 }
629
630 table->veto(unsupportedID);
631 {
632 Json::Value const unsupp =
633 table->getJson(unsupportedID, false)[to_string(unsupportedID)];
634 BEAST_EXPECT(!unsupp[jss::vetoed].asBool());
635 }
636
637 votes.emplace_back(testAmendment, validators.size());
638
639 votes.emplace_back(testAmendment, validators.size());
640
641 doRound(
642 env.current()->rules(),
643 *table,
644 weeks{2},
645 validators,
646 votes,
647 ourVotes,
648 enabled,
649 majority);
650 BEAST_EXPECT(ourVotes.empty());
651 BEAST_EXPECT(enabled.empty());
652
653 majority[testAmendment] = hourTime(weeks{1});
654
655 // Note that the simulation code assumes others behave as we do,
656 // so the amendment won't get enabled
657 doRound(
658 env.current()->rules(),
659 *table,
660 weeks{5},
661 validators,
662 votes,
663 ourVotes,
664 enabled,
665 majority);
666 BEAST_EXPECT(ourVotes.empty());
667 BEAST_EXPECT(enabled.empty());
668 }
669
670 // No vote on vetoed amendment
671 void
673 {
674 testcase("Vote NO on vetoed");
675
676 auto const testAmendment = amendmentId("vetoedAmendment");
677
678 test::jtx::Env env{*this, feat};
679 auto table = makeTable(
680 env,
681 weeks(2),
682 emptyYes_,
684 makeSection(testAmendment));
685
686 auto const validators = makeValidators(10, table);
687
689 std::vector<uint256> ourVotes;
690 std::set<uint256> enabled;
691 majorityAmendments_t majority;
692
693 doRound(
694 env.current()->rules(),
695 *table,
696 weeks{1},
697 validators,
698 votes,
699 ourVotes,
700 enabled,
701 majority);
702 BEAST_EXPECT(ourVotes.empty());
703 BEAST_EXPECT(enabled.empty());
704 BEAST_EXPECT(majority.empty());
705
706 votes.emplace_back(testAmendment, validators.size());
707
708 doRound(
709 env.current()->rules(),
710 *table,
711 weeks{2},
712 validators,
713 votes,
714 ourVotes,
715 enabled,
716 majority);
717 BEAST_EXPECT(ourVotes.empty());
718 BEAST_EXPECT(enabled.empty());
719
720 majority[testAmendment] = hourTime(weeks{1});
721
722 doRound(
723 env.current()->rules(),
724 *table,
725 weeks{5},
726 validators,
727 votes,
728 ourVotes,
729 enabled,
730 majority);
731 BEAST_EXPECT(ourVotes.empty());
732 BEAST_EXPECT(enabled.empty());
733 }
734
735 // Vote on and enable known, not-enabled amendment
736 void
738 {
739 testcase("voteEnable");
740
741 test::jtx::Env env{*this, feat};
742 auto table = makeTable(
744
745 auto const validators = makeValidators(10, table);
746
748 std::vector<uint256> ourVotes;
749 std::set<uint256> enabled;
750 majorityAmendments_t majority;
751
752 // Week 1: We should vote for all known amendments not enabled
753 doRound(
754 env.current()->rules(),
755 *table,
756 weeks{1},
757 validators,
758 votes,
759 ourVotes,
760 enabled,
761 majority);
762 BEAST_EXPECT(ourVotes.size() == yes_.size());
763 BEAST_EXPECT(enabled.empty());
764 for (auto const& i : yes_)
765 BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
766
767 // Now, everyone votes for this feature
768 for (auto const& i : yes_)
769 votes.emplace_back(amendmentId(i), validators.size());
770
771 // Week 2: We should recognize a majority
772 doRound(
773 env.current()->rules(),
774 *table,
775 weeks{2},
776 validators,
777 votes,
778 ourVotes,
779 enabled,
780 majority);
781 BEAST_EXPECT(ourVotes.size() == yes_.size());
782 BEAST_EXPECT(enabled.empty());
783
784 for (auto const& i : yes_)
785 BEAST_EXPECT(majority[amendmentId(i)] == hourTime(weeks{2}));
786
787 // Week 5: We should enable the amendment
788 doRound(
789 env.current()->rules(),
790 *table,
791 weeks{5},
792 validators,
793 votes,
794 ourVotes,
795 enabled,
796 majority);
797 BEAST_EXPECT(enabled.size() == yes_.size());
798
799 // Week 6: We should remove it from our votes and from having a majority
800 doRound(
801 env.current()->rules(),
802 *table,
803 weeks{6},
804 validators,
805 votes,
806 ourVotes,
807 enabled,
808 majority);
809 BEAST_EXPECT(enabled.size() == yes_.size());
810 BEAST_EXPECT(ourVotes.empty());
811 for (auto const& i : yes_)
812 BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
813 }
814
815 // Detect majority at 80%, enable later
816 void
818 {
819 testcase("detectMajority");
820
821 auto const testAmendment = amendmentId("detectMajority");
822 test::jtx::Env env{*this, feat};
823 auto table = makeTable(
824 env,
825 weeks(2),
826 makeDefaultYes(testAmendment),
829
830 auto const validators = makeValidators(16, table);
831
832 std::set<uint256> enabled;
833 majorityAmendments_t majority;
834
835 for (int i = 0; i <= 17; ++i)
836 {
838 std::vector<uint256> ourVotes;
839
840 if ((i > 0) && (i < 17))
841 votes.emplace_back(testAmendment, i);
842
843 doRound(
844 env.current()->rules(),
845 *table,
846 weeks{i},
847 validators,
848 votes,
849 ourVotes,
850 enabled,
851 majority);
852
853 if (i < 13) // 13 => 13/16 = 0.8125 => > 80%
854 {
855 // We are voting yes, not enabled, no majority
856 BEAST_EXPECT(!ourVotes.empty());
857 BEAST_EXPECT(enabled.empty());
858 BEAST_EXPECT(majority.empty());
859 }
860 else if (i < 15)
861 {
862 // We have a majority, not enabled, keep voting
863 BEAST_EXPECT(!ourVotes.empty());
864 BEAST_EXPECT(!majority.empty());
865 BEAST_EXPECT(enabled.empty());
866 }
867 else if (i == 15)
868 {
869 // enable, keep voting, remove from majority
870 BEAST_EXPECT(!ourVotes.empty());
871 BEAST_EXPECT(majority.empty());
872 BEAST_EXPECT(!enabled.empty());
873 }
874 else
875 {
876 // Done, we should be enabled and not voting
877 BEAST_EXPECT(ourVotes.empty());
878 BEAST_EXPECT(majority.empty());
879 BEAST_EXPECT(!enabled.empty());
880 }
881 }
882 }
883
884 // Detect loss of majority
885 void
887 {
888 testcase("lostMajority");
889
890 auto const testAmendment = amendmentId("lostMajority");
891
892 test::jtx::Env env{*this, feat};
893 auto table = makeTable(
894 env,
895 weeks(8),
896 makeDefaultYes(testAmendment),
899
900 auto const validators = makeValidators(16, table);
901
902 std::set<uint256> enabled;
903 majorityAmendments_t majority;
904
905 {
906 // establish majority
908 std::vector<uint256> ourVotes;
909
910 votes.emplace_back(testAmendment, validators.size());
911
912 doRound(
913 env.current()->rules(),
914 *table,
915 weeks{1},
916 validators,
917 votes,
918 ourVotes,
919 enabled,
920 majority);
921
922 BEAST_EXPECT(enabled.empty());
923 BEAST_EXPECT(!majority.empty());
924 }
925
926 for (int i = 1; i < 8; ++i)
927 {
929 std::vector<uint256> ourVotes;
930
931 // Gradually reduce support
932 votes.emplace_back(testAmendment, validators.size() - i);
933
934 doRound(
935 env.current()->rules(),
936 *table,
937 weeks{i + 1},
938 validators,
939 votes,
940 ourVotes,
941 enabled,
942 majority);
943
944 if (i < 4) // 16 - 3 = 13 => 13/16 = 0.8125 => > 80%
945 { // 16 - 4 = 12 => 12/16 = 0.75 => < 80%
946 // We are voting yes, not enabled, majority
947 BEAST_EXPECT(!ourVotes.empty());
948 BEAST_EXPECT(enabled.empty());
949 BEAST_EXPECT(!majority.empty());
950 }
951 else
952 {
953 // No majority, not enabled, keep voting
954 BEAST_EXPECT(!ourVotes.empty());
955 BEAST_EXPECT(majority.empty());
956 BEAST_EXPECT(enabled.empty());
957 }
958 }
959 }
960
961 // Exercise the UNL changing while voting is in progress.
962 void
964 {
965 testcase("changedUNL");
966
967 auto const testAmendment = amendmentId("changedUNL");
968 test::jtx::Env env{*this, feat};
969 auto table = makeTable(
970 env,
971 weeks(8),
972 makeDefaultYes(testAmendment),
975
977 makeValidators(10, table);
978
979 std::set<uint256> enabled;
980 majorityAmendments_t majority;
981
982 {
983 // 10 validators with 2 voting against won't get majority.
985 std::vector<uint256> ourVotes;
986
987 votes.emplace_back(testAmendment, validators.size() - 2);
988
989 doRound(
990 env.current()->rules(),
991 *table,
992 weeks{1},
993 validators,
994 votes,
995 ourVotes,
996 enabled,
997 majority);
998
999 BEAST_EXPECT(enabled.empty());
1000 BEAST_EXPECT(majority.empty());
1001 }
1002
1003 // Add one new validator to the UNL.
1005
1006 // A lambda that updates the AmendmentTable with the latest
1007 // trusted validators.
1008 auto callTrustChanged =
1009 [](std::vector<std::pair<PublicKey, SecretKey>> const& validators,
1010 std::unique_ptr<AmendmentTable> const& table) {
1011 // We need a hash_set to pass to trustChanged.
1012 hash_set<PublicKey> trustedValidators;
1013 trustedValidators.reserve(validators.size());
1015 validators.begin(),
1016 validators.end(),
1017 [&trustedValidators](auto const& val) {
1018 trustedValidators.insert(val.first);
1019 });
1020
1021 // Tell the AmendmentTable that the UNL changed.
1022 table->trustChanged(trustedValidators);
1023 };
1024
1025 // Tell the table that there's been a change in trusted validators.
1026 callTrustChanged(validators, table);
1027
1028 {
1029 // 11 validators with 2 voting against gains majority.
1031 std::vector<uint256> ourVotes;
1032
1033 votes.emplace_back(testAmendment, validators.size() - 2);
1034
1035 doRound(
1036 env.current()->rules(),
1037 *table,
1038 weeks{2},
1039 validators,
1040 votes,
1041 ourVotes,
1042 enabled,
1043 majority);
1044
1045 BEAST_EXPECT(enabled.empty());
1046 BEAST_EXPECT(!majority.empty());
1047 }
1048 {
1049 // One of the validators goes flaky and doesn't send validations
1050 // (without the UNL changing) so the amendment loses majority.
1051 std::pair<PublicKey, SecretKey> const savedValidator =
1052 validators.front();
1053 validators.erase(validators.begin());
1054
1056 std::vector<uint256> ourVotes;
1057
1058 votes.emplace_back(testAmendment, validators.size() - 2);
1059
1060 doRound(
1061 env.current()->rules(),
1062 *table,
1063 weeks{3},
1064 validators,
1065 votes,
1066 ourVotes,
1067 enabled,
1068 majority);
1069
1070 BEAST_EXPECT(enabled.empty());
1071 BEAST_EXPECT(majority.empty());
1072
1073 // Simulate the validator re-syncing to the network by adding it
1074 // back to the validators vector
1075 validators.insert(validators.begin(), savedValidator);
1076
1077 votes.front().second = validators.size() - 2;
1078
1079 doRound(
1080 env.current()->rules(),
1081 *table,
1082 weeks{4},
1083 validators,
1084 votes,
1085 ourVotes,
1086 enabled,
1087 majority);
1088
1089 BEAST_EXPECT(enabled.empty());
1090 BEAST_EXPECT(!majority.empty());
1091
1092 // Finally, remove one validator from the UNL and see that majority
1093 // is lost.
1094 validators.erase(validators.begin());
1095
1096 // Tell the table that there's been a change in trusted validators.
1097 callTrustChanged(validators, table);
1098
1099 votes.front().second = validators.size() - 2;
1100
1101 doRound(
1102 env.current()->rules(),
1103 *table,
1104 weeks{5},
1105 validators,
1106 votes,
1107 ourVotes,
1108 enabled,
1109 majority);
1110
1111 BEAST_EXPECT(enabled.empty());
1112 BEAST_EXPECT(majority.empty());
1113 }
1114 }
1115
1116 // Exercise a validator losing connectivity and then regaining it after
1117 // extended delays. Depending on how long that delay is an amendment
1118 // either will or will not go live.
1119 void
1121 {
1122 testcase("validatorFlapping");
1123
1124 // We run a test where a validator flaps on and off every 23 hours
1125 // and another one one where it flaps on and off every 25 hours.
1126 //
1127 // Since the local validator vote record expires after 24 hours,
1128 // with 23 hour flapping the amendment will go live. But with 25
1129 // hour flapping the amendment will not go live.
1130 for (int flapRateHours : {23, 25})
1131 {
1132 test::jtx::Env env{*this, feat};
1133 auto const testAmendment = amendmentId("validatorFlapping");
1134 auto table = makeTable(
1135 env,
1136 weeks(1),
1137 makeDefaultYes(testAmendment),
1140
1141 // Make two lists of validators, one with a missing validator, to
1142 // make it easy to simulate validator flapping.
1143 auto const allValidators = makeValidators(11, table);
1144 decltype(allValidators) const mostValidators(
1145 allValidators.begin() + 1, allValidators.end());
1146 BEAST_EXPECT(allValidators.size() == mostValidators.size() + 1);
1147
1148 std::set<uint256> enabled;
1149 majorityAmendments_t majority;
1150
1152 std::vector<uint256> ourVotes;
1153
1154 votes.emplace_back(testAmendment, allValidators.size() - 2);
1155
1156 int delay = flapRateHours;
1157 // Loop for 1 week plus a day.
1158 for (int hour = 1; hour < (24 * 8); ++hour)
1159 {
1160 decltype(allValidators) const& thisHoursValidators =
1161 (delay < flapRateHours) ? mostValidators : allValidators;
1162 delay = delay == flapRateHours ? 0 : delay + 1;
1163
1164 votes.front().second = thisHoursValidators.size() - 2;
1165
1166 using namespace std::chrono;
1167 doRound(
1168 env.current()->rules(),
1169 *table,
1170 hours(hour),
1171 thisHoursValidators,
1172 votes,
1173 ourVotes,
1174 enabled,
1175 majority);
1176
1177 if (hour <= (24 * 7) || flapRateHours > 24)
1178 {
1179 // The amendment should not be enabled under any
1180 // circumstance until one week has elapsed.
1181 BEAST_EXPECT(enabled.empty());
1182
1183 // If flapping is less than 24 hours, there should be
1184 // no flapping. Otherwise we should only have majority
1185 // if allValidators vote -- which means there are no
1186 // missing validators.
1187 bool const expectMajority = (delay <= 24)
1188 ? true
1189 : &thisHoursValidators == &allValidators;
1190 BEAST_EXPECT(majority.empty() != expectMajority);
1191 }
1192 else
1193 {
1194 // We're...
1195 // o Past one week, and
1196 // o AmendmentFlapping was less than 24 hours.
1197 // The amendment should be enabled.
1198 BEAST_EXPECT(!enabled.empty());
1199 BEAST_EXPECT(majority.empty());
1200 }
1201 }
1202 }
1203 }
1204
1205 void
1207 {
1208 testcase("hasUnsupportedEnabled");
1209
1210 using namespace std::chrono_literals;
1211 weeks constexpr w(1);
1212 test::jtx::Env env{*this, makeConfig()};
1213 auto table = makeTable(env, w);
1214 BEAST_EXPECT(!table->hasUnsupportedEnabled());
1215 BEAST_EXPECT(!table->firstUnsupportedExpected());
1216 BEAST_EXPECT(table->needValidatedLedger(1));
1217
1218 std::set<uint256> enabled;
1221 unsupported_.end(),
1222 [&enabled](auto const& s) { enabled.insert(amendmentId(s)); });
1223
1224 majorityAmendments_t majority;
1225 table->doValidatedLedger(1, enabled, majority);
1226 BEAST_EXPECT(table->hasUnsupportedEnabled());
1227 BEAST_EXPECT(!table->firstUnsupportedExpected());
1228
1229 NetClock::duration t{1000s};
1233 [&majority, &t](auto const& s) {
1234 majority[amendmentId(s)] = NetClock::time_point{--t};
1235 });
1236
1237 table->doValidatedLedger(1, enabled, majority);
1238 BEAST_EXPECT(table->hasUnsupportedEnabled());
1239 BEAST_EXPECT(
1240 table->firstUnsupportedExpected() &&
1241 *table->firstUnsupportedExpected() == NetClock::time_point{t} + w);
1242
1243 // Make sure the table knows when it needs an update.
1244 BEAST_EXPECT(!table->needValidatedLedger(256));
1245 BEAST_EXPECT(table->needValidatedLedger(257));
1246 }
1247
1248 void
1250 {
1251 testNoOnUnknown(feat);
1252 testNoOnVetoed(feat);
1253 testVoteEnable(feat);
1254 testDetectMajority(feat);
1255 testLostMajority(feat);
1256 testChangedUNL(feat);
1257 testValidatorFlapping(feat);
1258 }
1259
1260 void
1261 run() override
1262 {
1264
1265 testConstruct();
1266 testGet();
1267 testBadConfig();
1268 testEnableVeto();
1269 testHasUnsupported();
1270 testFeature(all);
1271 }
1272};
1273
1274BEAST_DEFINE_TESTSUITE(AmendmentTable, app, ripple);
1275
1276} // namespace ripple
T back_inserter(T... args)
T begin(T... args)
T capacity(T... args)
Represents a JSON value.
Definition json_value.h:130
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:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
void testNoOnVetoed(FeatureBitset const &feat)
static std::vector< AmendmentTable::FeatureInfo > makeObsolete(std::vector< std::string > const &amendments)
static Section makeSection(std::vector< std::string > const &amendments)
static uint256 amendmentId(std::string in)
void testDetectMajority(FeatureBitset const &feat)
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)
std::vector< std::string > const obsolete_
std::vector< std::string > const unsupportedMajority_
static void combine_arg(std::vector< Arg > &dest, std::vector< Arg > const &src, Args const &... args)
void testChangedUNL(FeatureBitset const &feat)
std::unique_ptr< AmendmentTable > makeTable(test::jtx::Env &env, std::chrono::seconds majorityTime)
std::vector< std::string > const enabled_
void testValidatorFlapping(FeatureBitset const &feat)
std::unique_ptr< Config > makeConfig()
std::vector< std::string > const vetoed_
void testFeature(FeatureBitset const &feat)
std::vector< std::string > const unsupported_
std::unique_ptr< AmendmentTable > makeTable(Application &app, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed)
static NetClock::time_point hourTime(std::chrono::hours h)
static Section makeSection(std::string const &name, std::vector< std::string > const &amendments)
static std::vector< Arg > combine(std::vector< Arg > left, std::vector< Arg > const &right, Args const &... args)
void testNoOnUnknown(FeatureBitset const &feat)
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)
static size_t totalsize(std::vector< Arg > const &src, Args const &... args)
static std::vector< AmendmentTable::FeatureInfo > makeFeatureInfo(std::vector< std::string > const &amendments, VoteBehavior voteBehavior)
std::vector< std::string > const allSupported_
static std::vector< AmendmentTable::FeatureInfo > makeDefaultNo(std::vector< std::string > const &amendments)
static std::vector< AmendmentTable::FeatureInfo > makeDefaultYes(uint256 const amendment)
static Section makeSection(uint256 const &amendment)
std::vector< std::string > const yes_
void testLostMajority(FeatureBitset const &feat)
std::vector< std::pair< PublicKey, SecretKey > > makeValidators(int num, std::unique_ptr< AmendmentTable > const &table)
std::vector< AmendmentTable::FeatureInfo > const emptyYes_
void run() override
Runs the suite.
void testVoteEnable(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:26
void append(std::vector< std::string > const &lines)
Append a set of lines to this section.
static constexpr std::size_t size()
Definition base_uint.h:507
A transaction testing environment.
Definition Env.h:102
Application & app()
Definition Env.h:242
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.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
FeatureBitset testable_amendments()
Definition Env.h:55
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
void hash_append(Hasher &h, Slice const &v)
Definition Slice.h:180
constexpr std::uint32_t tfGotMajority
Definition TxFlags.h:109
std::chrono::duration< int, std::ratio_multiply< days::period, std::ratio< 7 > > > weeks
Definition chrono.h:24
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
VoteBehavior
Definition Feature.h:68
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
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
T reserve(T... args)
T size(T... args)
T what(T... args)