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