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