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