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 (nVotes >= i)
549 {
550 // We vote yes on this amendment
551 field.push_back(hash);
552 }
553 }
554
557 pub,
558 sec,
559 calcNodeID(pub),
560 [&field](STValidation& v) {
561 if (!field.empty())
562 v.setFieldV256(
563 sfAmendments, STVector256(sfAmendments, field));
564 v.setFieldU32(sfLedgerSequence, 6180339);
565 });
566
567 validations.emplace_back(v);
568 }
569
570 ourVotes = table.doValidation(enabled);
571
572 auto actions =
573 table.doVoting(rules, roundTime, enabled, majority, validations);
574 for (auto const& [hash, action] : actions)
575 {
576 // This code assumes other validators do as we do
577
578 switch (action)
579 {
580 case 0:
581 // amendment goes from majority to enabled
582 if (enabled.find(hash) != enabled.end())
583 Throw<std::runtime_error>("enabling already enabled");
584 if (majority.find(hash) == majority.end())
585 Throw<std::runtime_error>("enabling without majority");
586 enabled.insert(hash);
587 majority.erase(hash);
588 break;
589
590 case tfGotMajority:
591 if (majority.find(hash) != majority.end())
592 Throw<std::runtime_error>(
593 "got majority while having majority");
594 majority[hash] = roundTime;
595 break;
596
597 case tfLostMajority:
598 if (majority.find(hash) == majority.end())
599 Throw<std::runtime_error>(
600 "lost majority without majority");
601 majority.erase(hash);
602 break;
603
604 default:
605 Throw<std::runtime_error>("unknown action");
606 }
607 }
608 }
609
610 // No vote on unknown amendment
611 void
613 {
614 testcase("Vote NO on unknown");
615
616 auto const testAmendment = amendmentId("TestAmendment");
617
618 test::jtx::Env env{*this, feat};
619 auto table =
621
622 auto const validators = makeValidators(10, table);
623
625 std::vector<uint256> ourVotes;
626 std::set<uint256> enabled;
627 majorityAmendments_t majority;
628
629 doRound(
630 env.current()->rules(),
631 *table,
632 weeks{1},
633 validators,
634 votes,
635 ourVotes,
636 enabled,
637 majority);
638 BEAST_EXPECT(ourVotes.empty());
639 BEAST_EXPECT(enabled.empty());
640 BEAST_EXPECT(majority.empty());
641
642 uint256 const unsupportedID = amendmentId(unsupported_[0]);
643 {
644 Json::Value const unsupp =
645 table->getJson(unsupportedID, false)[to_string(unsupportedID)];
646 BEAST_EXPECT(unsupp.size() == 0);
647 }
648
649 table->veto(unsupportedID);
650 {
651 Json::Value const unsupp =
652 table->getJson(unsupportedID, false)[to_string(unsupportedID)];
653 BEAST_EXPECT(!unsupp[jss::vetoed].asBool());
654 }
655
656 votes.emplace_back(testAmendment, validators.size());
657
658 votes.emplace_back(testAmendment, validators.size());
659
660 doRound(
661 env.current()->rules(),
662 *table,
663 weeks{2},
664 validators,
665 votes,
666 ourVotes,
667 enabled,
668 majority);
669 BEAST_EXPECT(ourVotes.empty());
670 BEAST_EXPECT(enabled.empty());
671
672 majority[testAmendment] = hourTime(weeks{1});
673
674 // Note that the simulation code assumes others behave as we do,
675 // so the amendment won't get enabled
676 doRound(
677 env.current()->rules(),
678 *table,
679 weeks{5},
680 validators,
681 votes,
682 ourVotes,
683 enabled,
684 majority);
685 BEAST_EXPECT(ourVotes.empty());
686 BEAST_EXPECT(enabled.empty());
687 }
688
689 // No vote on vetoed amendment
690 void
692 {
693 testcase("Vote NO on vetoed");
694
695 auto const testAmendment = amendmentId("vetoedAmendment");
696
697 test::jtx::Env env{*this, feat};
698 auto table = makeTable(
699 env,
700 weeks(2),
701 emptyYes_,
703 makeSection(testAmendment));
704
705 auto const validators = makeValidators(10, table);
706
708 std::vector<uint256> ourVotes;
709 std::set<uint256> enabled;
710 majorityAmendments_t majority;
711
712 doRound(
713 env.current()->rules(),
714 *table,
715 weeks{1},
716 validators,
717 votes,
718 ourVotes,
719 enabled,
720 majority);
721 BEAST_EXPECT(ourVotes.empty());
722 BEAST_EXPECT(enabled.empty());
723 BEAST_EXPECT(majority.empty());
724
725 votes.emplace_back(testAmendment, validators.size());
726
727 doRound(
728 env.current()->rules(),
729 *table,
730 weeks{2},
731 validators,
732 votes,
733 ourVotes,
734 enabled,
735 majority);
736 BEAST_EXPECT(ourVotes.empty());
737 BEAST_EXPECT(enabled.empty());
738
739 majority[testAmendment] = hourTime(weeks{1});
740
741 doRound(
742 env.current()->rules(),
743 *table,
744 weeks{5},
745 validators,
746 votes,
747 ourVotes,
748 enabled,
749 majority);
750 BEAST_EXPECT(ourVotes.empty());
751 BEAST_EXPECT(enabled.empty());
752 }
753
754 // Vote on and enable known, not-enabled amendment
755 void
757 {
758 testcase("voteEnable");
759
760 test::jtx::Env env{*this, feat};
761 auto table = makeTable(
763
764 auto const validators = makeValidators(10, table);
765
767 std::vector<uint256> ourVotes;
768 std::set<uint256> enabled;
769 majorityAmendments_t majority;
770
771 // Week 1: We should vote for all known amendments not enabled
772 doRound(
773 env.current()->rules(),
774 *table,
775 weeks{1},
776 validators,
777 votes,
778 ourVotes,
779 enabled,
780 majority);
781 BEAST_EXPECT(ourVotes.size() == yes_.size());
782 BEAST_EXPECT(enabled.empty());
783 for (auto const& i : yes_)
784 BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
785
786 // Now, everyone votes for this feature
787 for (auto const& i : yes_)
788 votes.emplace_back(amendmentId(i), validators.size());
789
790 // Week 2: We should recognize a majority
791 doRound(
792 env.current()->rules(),
793 *table,
794 weeks{2},
795 validators,
796 votes,
797 ourVotes,
798 enabled,
799 majority);
800 BEAST_EXPECT(ourVotes.size() == yes_.size());
801 BEAST_EXPECT(enabled.empty());
802
803 for (auto const& i : yes_)
804 BEAST_EXPECT(majority[amendmentId(i)] == hourTime(weeks{2}));
805
806 // Week 5: We should enable the amendment
807 doRound(
808 env.current()->rules(),
809 *table,
810 weeks{5},
811 validators,
812 votes,
813 ourVotes,
814 enabled,
815 majority);
816 BEAST_EXPECT(enabled.size() == yes_.size());
817
818 // Week 6: We should remove it from our votes and from having a majority
819 doRound(
820 env.current()->rules(),
821 *table,
822 weeks{6},
823 validators,
824 votes,
825 ourVotes,
826 enabled,
827 majority);
828 BEAST_EXPECT(enabled.size() == yes_.size());
829 BEAST_EXPECT(ourVotes.empty());
830 for (auto const& i : yes_)
831 BEAST_EXPECT(majority.find(amendmentId(i)) == majority.end());
832 }
833
834 // Detect majority at 80%, enable later
835 void
837 {
838 testcase("detectMajority");
839
840 auto const testAmendment = amendmentId("detectMajority");
841 test::jtx::Env env{*this, feat};
842 auto table = makeTable(
843 env,
844 weeks(2),
845 makeDefaultYes(testAmendment),
848
849 auto const validators = makeValidators(16, table);
850
851 std::set<uint256> enabled;
852 majorityAmendments_t majority;
853
854 for (int i = 0; i <= 17; ++i)
855 {
857 std::vector<uint256> ourVotes;
858
859 if ((i > 0) && (i < 17))
860 votes.emplace_back(testAmendment, i);
861
862 doRound(
863 env.current()->rules(),
864 *table,
865 weeks{i},
866 validators,
867 votes,
868 ourVotes,
869 enabled,
870 majority);
871
872 if (i < 13) // 13 => 13/16 = 0.8125 => > 80%
873 {
874 // We are voting yes, not enabled, no majority
875 BEAST_EXPECT(!ourVotes.empty());
876 BEAST_EXPECT(enabled.empty());
877 BEAST_EXPECT(majority.empty());
878 }
879 else if (i < 15)
880 {
881 // We have a majority, not enabled, keep voting
882 BEAST_EXPECT(!ourVotes.empty());
883 BEAST_EXPECT(!majority.empty());
884 BEAST_EXPECT(enabled.empty());
885 }
886 else if (i == 15)
887 {
888 // enable, keep voting, remove from majority
889 BEAST_EXPECT(!ourVotes.empty());
890 BEAST_EXPECT(majority.empty());
891 BEAST_EXPECT(!enabled.empty());
892 }
893 else
894 {
895 // Done, we should be enabled and not voting
896 BEAST_EXPECT(ourVotes.empty());
897 BEAST_EXPECT(majority.empty());
898 BEAST_EXPECT(!enabled.empty());
899 }
900 }
901 }
902
903 // Detect loss of majority
904 void
906 {
907 testcase("lostMajority");
908
909 auto const testAmendment = amendmentId("lostMajority");
910
911 test::jtx::Env env{*this, feat};
912 auto table = makeTable(
913 env,
914 weeks(8),
915 makeDefaultYes(testAmendment),
918
919 auto const validators = makeValidators(16, table);
920
921 std::set<uint256> enabled;
922 majorityAmendments_t majority;
923
924 {
925 // establish majority
927 std::vector<uint256> ourVotes;
928
929 votes.emplace_back(testAmendment, validators.size());
930
931 doRound(
932 env.current()->rules(),
933 *table,
934 weeks{1},
935 validators,
936 votes,
937 ourVotes,
938 enabled,
939 majority);
940
941 BEAST_EXPECT(enabled.empty());
942 BEAST_EXPECT(!majority.empty());
943 }
944
945 for (int i = 1; i < 8; ++i)
946 {
948 std::vector<uint256> ourVotes;
949
950 // Gradually reduce support
951 votes.emplace_back(testAmendment, validators.size() - i);
952
953 doRound(
954 env.current()->rules(),
955 *table,
956 weeks{i + 1},
957 validators,
958 votes,
959 ourVotes,
960 enabled,
961 majority);
962
963 if (i < 4) // 16 - 3 = 13 => 13/16 = 0.8125 => > 80%
964 { // 16 - 4 = 12 => 12/16 = 0.75 => < 80%
965 // We are voting yes, not enabled, majority
966 BEAST_EXPECT(!ourVotes.empty());
967 BEAST_EXPECT(enabled.empty());
968 BEAST_EXPECT(!majority.empty());
969 }
970 else
971 {
972 // No majority, not enabled, keep voting
973 BEAST_EXPECT(!ourVotes.empty());
974 BEAST_EXPECT(majority.empty());
975 BEAST_EXPECT(enabled.empty());
976 }
977 }
978 }
979
980 // Exercise the UNL changing while voting is in progress.
981 void
983 {
984 testcase("changedUNL");
985
986 auto const testAmendment = amendmentId("changedUNL");
987 test::jtx::Env env{*this, feat};
988 auto table = makeTable(
989 env,
990 weeks(8),
991 makeDefaultYes(testAmendment),
994
996 makeValidators(10, table);
997
998 std::set<uint256> enabled;
999 majorityAmendments_t majority;
1000
1001 {
1002 // 10 validators with 2 voting against won't get majority.
1004 std::vector<uint256> ourVotes;
1005
1006 votes.emplace_back(testAmendment, validators.size() - 2);
1007
1008 doRound(
1009 env.current()->rules(),
1010 *table,
1011 weeks{1},
1012 validators,
1013 votes,
1014 ourVotes,
1015 enabled,
1016 majority);
1017
1018 BEAST_EXPECT(enabled.empty());
1019 BEAST_EXPECT(majority.empty());
1020 }
1021
1022 // Add one new validator to the UNL.
1024
1025 // A lambda that updates the AmendmentTable with the latest
1026 // trusted validators.
1027 auto callTrustChanged =
1028 [](std::vector<std::pair<PublicKey, SecretKey>> const& validators,
1029 std::unique_ptr<AmendmentTable> const& table) {
1030 // We need a hash_set to pass to trustChanged.
1031 hash_set<PublicKey> trustedValidators;
1032 trustedValidators.reserve(validators.size());
1034 validators.begin(),
1035 validators.end(),
1036 [&trustedValidators](auto const& val) {
1037 trustedValidators.insert(val.first);
1038 });
1039
1040 // Tell the AmendmentTable that the UNL changed.
1041 table->trustChanged(trustedValidators);
1042 };
1043
1044 // Tell the table that there's been a change in trusted validators.
1045 callTrustChanged(validators, table);
1046
1047 {
1048 // 11 validators with 2 voting against gains majority.
1050 std::vector<uint256> ourVotes;
1051
1052 votes.emplace_back(testAmendment, validators.size() - 2);
1053
1054 doRound(
1055 env.current()->rules(),
1056 *table,
1057 weeks{2},
1058 validators,
1059 votes,
1060 ourVotes,
1061 enabled,
1062 majority);
1063
1064 BEAST_EXPECT(enabled.empty());
1065 BEAST_EXPECT(!majority.empty());
1066 }
1067 {
1068 // One of the validators goes flaky and doesn't send validations
1069 // (without the UNL changing) so the amendment loses majority.
1070 std::pair<PublicKey, SecretKey> const savedValidator =
1071 validators.front();
1072 validators.erase(validators.begin());
1073
1075 std::vector<uint256> ourVotes;
1076
1077 votes.emplace_back(testAmendment, validators.size() - 2);
1078
1079 doRound(
1080 env.current()->rules(),
1081 *table,
1082 weeks{3},
1083 validators,
1084 votes,
1085 ourVotes,
1086 enabled,
1087 majority);
1088
1089 BEAST_EXPECT(enabled.empty());
1090 BEAST_EXPECT(majority.empty());
1091
1092 // Simulate the validator re-syncing to the network by adding it
1093 // back to the validators vector
1094 validators.insert(validators.begin(), savedValidator);
1095
1096 votes.front().second = validators.size() - 2;
1097
1098 doRound(
1099 env.current()->rules(),
1100 *table,
1101 weeks{4},
1102 validators,
1103 votes,
1104 ourVotes,
1105 enabled,
1106 majority);
1107
1108 BEAST_EXPECT(enabled.empty());
1109 BEAST_EXPECT(!majority.empty());
1110
1111 // Finally, remove one validator from the UNL and see that majority
1112 // is lost.
1113 validators.erase(validators.begin());
1114
1115 // Tell the table that there's been a change in trusted validators.
1116 callTrustChanged(validators, table);
1117
1118 votes.front().second = validators.size() - 2;
1119
1120 doRound(
1121 env.current()->rules(),
1122 *table,
1123 weeks{5},
1124 validators,
1125 votes,
1126 ourVotes,
1127 enabled,
1128 majority);
1129
1130 BEAST_EXPECT(enabled.empty());
1131 BEAST_EXPECT(majority.empty());
1132 }
1133 }
1134
1135 // Exercise a validator losing connectivity and then regaining it after
1136 // extended delays. Depending on how long that delay is an amendment
1137 // either will or will not go live.
1138 void
1140 {
1141 testcase("validatorFlapping");
1142
1143 // We run a test where a validator flaps on and off every 23 hours
1144 // and another one one where it flaps on and off every 25 hours.
1145 //
1146 // Since the local validator vote record expires after 24 hours,
1147 // with 23 hour flapping the amendment will go live. But with 25
1148 // hour flapping the amendment will not go live.
1149 for (int flapRateHours : {23, 25})
1150 {
1151 test::jtx::Env env{*this, feat};
1152 auto const testAmendment = amendmentId("validatorFlapping");
1153 auto table = makeTable(
1154 env,
1155 weeks(1),
1156 makeDefaultYes(testAmendment),
1159
1160 // Make two lists of validators, one with a missing validator, to
1161 // make it easy to simulate validator flapping.
1162 auto const allValidators = makeValidators(11, table);
1163 decltype(allValidators) const mostValidators(
1164 allValidators.begin() + 1, allValidators.end());
1165 BEAST_EXPECT(allValidators.size() == mostValidators.size() + 1);
1166
1167 std::set<uint256> enabled;
1168 majorityAmendments_t majority;
1169
1171 std::vector<uint256> ourVotes;
1172
1173 votes.emplace_back(testAmendment, allValidators.size() - 2);
1174
1175 int delay = flapRateHours;
1176 // Loop for 1 week plus a day.
1177 for (int hour = 1; hour < (24 * 8); ++hour)
1178 {
1179 decltype(allValidators) const& thisHoursValidators =
1180 (delay < flapRateHours) ? mostValidators : allValidators;
1181 delay = delay == flapRateHours ? 0 : delay + 1;
1182
1183 votes.front().second = thisHoursValidators.size() - 2;
1184
1185 using namespace std::chrono;
1186 doRound(
1187 env.current()->rules(),
1188 *table,
1189 hours(hour),
1190 thisHoursValidators,
1191 votes,
1192 ourVotes,
1193 enabled,
1194 majority);
1195
1196 if (hour <= (24 * 7) || flapRateHours > 24)
1197 {
1198 // The amendment should not be enabled under any
1199 // circumstance until one week has elapsed.
1200 BEAST_EXPECT(enabled.empty());
1201
1202 // If flapping is less than 24 hours, there should be
1203 // no flapping. Otherwise we should only have majority
1204 // if allValidators vote -- which means there are no
1205 // missing validators.
1206 bool const expectMajority = (delay <= 24)
1207 ? true
1208 : &thisHoursValidators == &allValidators;
1209 BEAST_EXPECT(majority.empty() != expectMajority);
1210 }
1211 else
1212 {
1213 // We're...
1214 // o Past one week, and
1215 // o AmendmentFlapping was less than 24 hours.
1216 // The amendment should be enabled.
1217 BEAST_EXPECT(!enabled.empty());
1218 BEAST_EXPECT(majority.empty());
1219 }
1220 }
1221 }
1222 }
1223
1224 void
1226 {
1227 testcase("hasUnsupportedEnabled");
1228
1229 using namespace std::chrono_literals;
1230 weeks constexpr w(1);
1231 test::jtx::Env env{*this, makeConfig()};
1232 auto table = makeTable(env, w);
1233 BEAST_EXPECT(!table->hasUnsupportedEnabled());
1234 BEAST_EXPECT(!table->firstUnsupportedExpected());
1235 BEAST_EXPECT(table->needValidatedLedger(1));
1236
1237 std::set<uint256> enabled;
1240 unsupported_.end(),
1241 [&enabled](auto const& s) { enabled.insert(amendmentId(s)); });
1242
1243 majorityAmendments_t majority;
1244 table->doValidatedLedger(1, enabled, majority);
1245 BEAST_EXPECT(table->hasUnsupportedEnabled());
1246 BEAST_EXPECT(!table->firstUnsupportedExpected());
1247
1248 NetClock::duration t{1000s};
1252 [&majority, &t](auto const& s) {
1253 majority[amendmentId(s)] = NetClock::time_point{--t};
1254 });
1255
1256 table->doValidatedLedger(1, enabled, majority);
1257 BEAST_EXPECT(table->hasUnsupportedEnabled());
1258 BEAST_EXPECT(
1259 table->firstUnsupportedExpected() &&
1260 *table->firstUnsupportedExpected() == NetClock::time_point{t} + w);
1261
1262 // Make sure the table knows when it needs an update.
1263 BEAST_EXPECT(!table->needValidatedLedger(256));
1264 BEAST_EXPECT(table->needValidatedLedger(257));
1265 }
1266
1267 void
1269 {
1270 testNoOnUnknown(feat);
1271 testNoOnVetoed(feat);
1272 testVoteEnable(feat);
1273 testDetectMajority(feat);
1274 testLostMajority(feat);
1275 testChangedUNL(feat);
1276 testValidatorFlapping(feat);
1277 }
1278
1279 void
1280 run() override
1281 {
1283
1284 testConstruct();
1285 testGet();
1286 testBadConfig();
1287 testEnableVeto();
1288 testHasUnsupported();
1289 testFeature(all);
1290 }
1291};
1292
1293BEAST_DEFINE_TESTSUITE(AmendmentTable, app, ripple);
1294
1295} // namespace ripple
T back_inserter(T... args)
T begin(T... args)
T capacity(T... args)
Represents a JSON value.
Definition json_value.h:149
UInt size() const
Number of values in array or object.
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
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.
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 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:54
FeatureBitset testable_amendments()
Definition Env.h:74
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
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.
VoteBehavior
Definition Feature.h:87
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.
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)
T what(T... args)