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