rippled
Loading...
Searching...
No Matches
NegativeUNL_test.cpp
1#include <test/jtx.h>
2
3#include <xrpld/app/consensus/RCLValidations.h>
4#include <xrpld/app/ledger/Ledger.h>
5#include <xrpld/app/misc/NegativeUNLVote.h>
6#include <xrpld/app/misc/ValidatorList.h>
7#include <xrpld/app/tx/apply.h>
8
9#include <xrpl/beast/unit_test.h>
10#include <xrpl/ledger/View.h>
11
12namespace xrpl {
13namespace test {
14
15/*
16 * This file implements the following negative UNL related tests:
17 * -- test filling and applying ttUNL_MODIFY Tx and ledger update
18 * -- test the NegativeUNLVote class. The test cases are split to multiple
19 * test classes to allow parallel execution.
20 * -- test the negativeUNLFilter function
21 *
22 * Other negative UNL related tests such as ValidatorList and RPC related ones
23 * are put in their existing unit test files.
24 */
25
36bool
39 size_t size,
40 bool hasToDisable,
41 bool hasToReEnable);
42
52bool
53applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
54
64bool
67 hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq);
68
77
86
95STTx
96createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
97
99{
107 void
109 {
110 /*
111 * test cases:
112 *
113 * (1) the ledger after genesis
114 * -- cannot apply Disable Tx
115 * -- cannot apply ReEnable Tx
116 * -- nUNL empty
117 * -- no ToDisable
118 * -- no ToReEnable
119 *
120 * (2) a flag ledger
121 * -- apply an Disable Tx
122 * -- cannot apply the second Disable Tx
123 * -- cannot apply a ReEnable Tx
124 * -- nUNL empty
125 * -- has ToDisable with right nodeId
126 * -- no ToReEnable
127 * ++ extra test: first Disable Tx in ledger TxSet
128 *
129 * (3) ledgers before the next flag ledger
130 * -- nUNL empty
131 * -- has ToDisable with right nodeId
132 * -- no ToReEnable
133 *
134 * (4) next flag ledger
135 * -- nUNL size == 1, with right nodeId
136 * -- no ToDisable
137 * -- no ToReEnable
138 * -- cannot apply an Disable Tx with nodeId already in nUNL
139 * -- apply an Disable Tx with different nodeId
140 * -- cannot apply a ReEnable Tx with the same NodeId as Add
141 * -- cannot apply a ReEnable Tx with a NodeId not in nUNL
142 * -- apply a ReEnable Tx with a nodeId already in nUNL
143 * -- has ToDisable with right nodeId
144 * -- has ToReEnable with right nodeId
145 * -- nUNL size still 1, right nodeId
146 *
147 * (5) ledgers before the next flag ledger
148 * -- nUNL size == 1, right nodeId
149 * -- has ToDisable with right nodeId
150 * -- has ToReEnable with right nodeId
151 *
152 * (6) next flag ledger
153 * -- nUNL size == 1, different nodeId
154 * -- no ToDisable
155 * -- no ToReEnable
156 * -- apply an Disable Tx with different nodeId
157 * -- nUNL size still 1, right nodeId
158 * -- has ToDisable with right nodeId
159 * -- no ToReEnable
160 *
161 * (7) ledgers before the next flag ledger
162 * -- nUNL size still 1, right nodeId
163 * -- has ToDisable with right nodeId
164 * -- no ToReEnable
165 *
166 * (8) next flag ledger
167 * -- nUNL size == 2
168 * -- apply a ReEnable Tx
169 * -- cannot apply second ReEnable Tx, even with right nodeId
170 * -- cannot apply an Disable Tx with the same NodeId as Remove
171 * -- nUNL size == 2
172 * -- no ToDisable
173 * -- has ToReEnable with right nodeId
174 *
175 * (9) ledgers before the next flag ledger
176 * -- nUNL size == 2
177 * -- no ToDisable
178 * -- has ToReEnable with right nodeId
179 *
180 * (10) next flag ledger
181 * -- nUNL size == 1
182 * -- apply a ReEnable Tx
183 * -- nUNL size == 1
184 * -- no ToDisable
185 * -- has ToReEnable with right nodeId
186 *
187 * (11) ledgers before the next flag ledger
188 * -- nUNL size == 1
189 * -- no ToDisable
190 * -- has ToReEnable with right nodeId
191 *
192 * (12) next flag ledger
193 * -- nUNL size == 0
194 * -- no ToDisable
195 * -- no ToReEnable
196 *
197 * (13) ledgers before the next flag ledger
198 * -- nUNL size == 0
199 * -- no ToDisable
200 * -- no ToReEnable
201 *
202 * (14) next flag ledger
203 * -- nUNL size == 0
204 * -- no ToDisable
205 * -- no ToReEnable
206 */
207
208 testcase("Create UNLModify Tx and apply to ledgers");
209
212 // genesis ledger
215 env.app().config(),
217 env.app().getNodeFamily());
218
219 // Record the public keys and ledger sequences of expected negative UNL
220 // validators when we build the ledger history
222
223 {
224 //(1) the ledger after genesis, not a flag ledger
226 *l, env.app().timeKeeper().closeTime());
227
228 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
229 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
230
231 OpenView accum(&*l);
232 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
233 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
234 accum.apply(*l);
235 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
236 }
237
238 {
239 //(2) a flag ledger
240 // generate more ledgers
241 for (auto i = 0; i < 256 - 2; ++i)
242 {
244 *l, env.app().timeKeeper().closeTime());
245 }
246 BEAST_EXPECT(l->isFlagLedger());
247 l->updateNegativeUNL();
248
249 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
250 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
251 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
252
253 // can apply 1 and only 1 ToDisable Tx,
254 // cannot apply ToReEnable Tx, since negative UNL is empty
255 OpenView accum(&*l);
256 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
257 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, false));
258 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
259 accum.apply(*l);
260 auto good_size = negUnlSizeTest(l, 0, true, false);
261 BEAST_EXPECT(good_size);
262 if (good_size)
263 {
264 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
265 //++ first ToDisable Tx in ledger's TxSet
266 uint256 txID = txDisable_0.getTransactionID();
267 BEAST_EXPECT(l->txExists(txID));
268 }
269 }
270
271 {
272 //(3) ledgers before the next flag ledger
273 for (auto i = 0; i < 256; ++i)
274 {
275 auto good_size = negUnlSizeTest(l, 0, true, false);
276 BEAST_EXPECT(good_size);
277 if (good_size)
278 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
280 *l, env.app().timeKeeper().closeTime());
281 }
282 BEAST_EXPECT(l->isFlagLedger());
283 l->updateNegativeUNL();
284
285 //(4) next flag ledger
286 // test if the ledger updated correctly
287 auto good_size = negUnlSizeTest(l, 1, false, false);
288 BEAST_EXPECT(good_size);
289 if (good_size)
290 {
291 BEAST_EXPECT(*(l->negativeUNL().begin()) == publicKeys[0]);
292 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
293 }
294
295 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
296 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
297 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
298 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
299 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
300
301 OpenView accum(&*l);
302 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
303 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, true));
304 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
305 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
306 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
307 accum.apply(*l);
308 good_size = negUnlSizeTest(l, 1, true, true);
309 BEAST_EXPECT(good_size);
310 if (good_size)
311 {
312 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
313 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
314 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
315 // test sfFirstLedgerSequence
316 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
317 }
318 }
319
320 {
321 //(5) ledgers before the next flag ledger
322 for (auto i = 0; i < 256; ++i)
323 {
324 auto good_size = negUnlSizeTest(l, 1, true, true);
325 BEAST_EXPECT(good_size);
326 if (good_size)
327 {
328 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
329 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
330 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
331 }
333 *l, env.app().timeKeeper().closeTime());
334 }
335 BEAST_EXPECT(l->isFlagLedger());
336 l->updateNegativeUNL();
337
338 //(6) next flag ledger
339 // test if the ledger updated correctly
340 auto good_size = negUnlSizeTest(l, 1, false, false);
341 BEAST_EXPECT(good_size);
342 if (good_size)
343 {
344 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
345 }
346
347 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
348
349 OpenView accum(&*l);
350 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
351 accum.apply(*l);
352 good_size = negUnlSizeTest(l, 1, true, false);
353 BEAST_EXPECT(good_size);
354 if (good_size)
355 {
356 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
357 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
358 nUnlLedgerSeq.emplace(publicKeys[1], l->seq());
359 nUnlLedgerSeq.erase(publicKeys[0]);
360 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
361 }
362 }
363
364 {
365 //(7) ledgers before the next flag ledger
366 for (auto i = 0; i < 256; ++i)
367 {
368 auto good_size = negUnlSizeTest(l, 1, true, false);
369 BEAST_EXPECT(good_size);
370 if (good_size)
371 {
372 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
373 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
374 }
376 *l, env.app().timeKeeper().closeTime());
377 }
378 BEAST_EXPECT(l->isFlagLedger());
379 l->updateNegativeUNL();
380
381 //(8) next flag ledger
382 // test if the ledger updated correctly
383 auto good_size = negUnlSizeTest(l, 2, false, false);
384 BEAST_EXPECT(good_size);
385 if (good_size)
386 {
387 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
388 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
389 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
390 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
391 }
392
393 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
394 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
395 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
396
397 OpenView accum(&*l);
398 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
399 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
400 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
401 accum.apply(*l);
402 good_size = negUnlSizeTest(l, 2, false, true);
403 BEAST_EXPECT(good_size);
404 if (good_size)
405 {
406 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
407 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
408 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
409 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
410 }
411 }
412
413 {
414 //(9) ledgers before the next flag ledger
415 for (auto i = 0; i < 256; ++i)
416 {
417 auto good_size = negUnlSizeTest(l, 2, false, true);
418 BEAST_EXPECT(good_size);
419 if (good_size)
420 {
421 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
422 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
423 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
424 }
426 *l, env.app().timeKeeper().closeTime());
427 }
428 BEAST_EXPECT(l->isFlagLedger());
429 l->updateNegativeUNL();
430
431 //(10) next flag ledger
432 // test if the ledger updated correctly
433 auto good_size = negUnlSizeTest(l, 1, false, false);
434 BEAST_EXPECT(good_size);
435 if (good_size)
436 {
437 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
438 nUnlLedgerSeq.erase(publicKeys[0]);
439 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
440 }
441
442 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
443
444 OpenView accum(&*l);
445 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, true));
446 accum.apply(*l);
447 good_size = negUnlSizeTest(l, 1, false, true);
448 BEAST_EXPECT(good_size);
449 if (good_size)
450 {
451 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
452 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
453 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
454 }
455 }
456
457 {
458 //(11) ledgers before the next flag ledger
459 for (auto i = 0; i < 256; ++i)
460 {
461 auto good_size = negUnlSizeTest(l, 1, false, true);
462 BEAST_EXPECT(good_size);
463 if (good_size)
464 {
465 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
466 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
467 }
469 *l, env.app().timeKeeper().closeTime());
470 }
471 BEAST_EXPECT(l->isFlagLedger());
472 l->updateNegativeUNL();
473
474 //(12) next flag ledger
475 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
476 }
477
478 {
479 //(13) ledgers before the next flag ledger
480 for (auto i = 0; i < 256; ++i)
481 {
482 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
484 *l, env.app().timeKeeper().closeTime());
485 }
486 BEAST_EXPECT(l->isFlagLedger());
487 l->updateNegativeUNL();
488
489 //(14) next flag ledger
490 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
491 }
492 }
493
494 void
495 run() override
496 {
498 }
499};
500
505{
513 {
514 std::uint32_t numNodes; // number of validators
515 std::uint32_t negUNLSize; // size of negative UNL in the last ledger
516 bool hasToDisable; // if has ToDisable in the last ledger
517 bool hasToReEnable; // if has ToReEnable in the last ledger
523 };
524
526 : env(suite, jtx::testable_amendments())
527 , param(p)
528 , validations(env.app().getValidations())
529 {
530 createNodes();
531 if (!param.numLedgers)
532 param.numLedgers = 256 * (param.negUNLSize + 1);
534 }
535
536 void
538 {
539 assert(param.numNodes <= 256);
541 for (int i = 0; i < param.numNodes; ++i)
542 {
543 UNLKeySet.insert(UNLKeys[i]);
544 UNLNodeIDs.push_back(calcNodeID(UNLKeys[i]));
545 UNLNodeIDSet.insert(UNLNodeIDs.back());
546 }
547 }
548
553 bool
555 {
556 static uint256 fake_amendment; // So we have different genesis ledgers
559 env.app().config(),
560 std::vector<uint256>{fake_amendment++},
561 env.app().getNodeFamily());
563
564 // When putting validators into the negative UNL, we start with
565 // validator 0, then validator 1 ...
566 int nidx = 0;
567 while (l->seq() <= param.numLedgers)
568 {
570 *l, env.app().timeKeeper().closeTime());
572
573 if (l->isFlagLedger())
574 {
575 l->updateNegativeUNL();
576 OpenView accum(&*l);
577 if (l->negativeUNL().size() < param.negUNLSize)
578 {
579 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
580 if (!applyAndTestResult(env, accum, tx, true))
581 break;
582 ++nidx;
583 }
584 else if (l->negativeUNL().size() == param.negUNLSize)
585 {
587 {
588 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
589 if (!applyAndTestResult(env, accum, tx, true))
590 break;
591 ++nidx;
592 }
594 {
595 auto tx = createTx(false, l->seq(), UNLKeys[0]);
596 if (!applyAndTestResult(env, accum, tx, true))
597 break;
598 }
599 }
600 accum.apply(*l);
601 }
602 l->updateSkipList();
603 }
604 return negUnlSizeTest(
606 }
607
616 {
617 static auto keyPair = randomKeyPair(KeyType::secp256k1);
619 env.app().timeKeeper().now(),
620 keyPair.first,
621 keyPair.second,
622 v,
623 [&](STValidation& v) {
624 v.setFieldH256(sfLedgerHash, ledger->header().hash);
625 v.setFieldU32(sfLedgerSequence, ledger->seq());
626 v.setFlag(vfFullValidation);
627 });
628 };
629
637 template <class NeedValidation>
638 void
639 walkHistoryAndAddValidations(NeedValidation&& needVal)
640 {
641 std::uint32_t curr = 0;
642 std::size_t need = 256 + 1;
643 // only last 256 + 1 ledgers need validations
644 if (history.size() > need)
645 curr = history.size() - need;
646 for (; curr != history.size(); ++curr)
647 {
648 for (std::size_t i = 0; i < param.numNodes; ++i)
649 {
650 if (needVal(history[curr], i))
651 {
653 v.setTrusted();
655 }
656 }
657 }
658 }
659
662 {
663 return history.back();
664 }
665
675};
676
677auto defaultPreVote = [](NegativeUNLVote& vote) {};
689template <typename PreVote = decltype(defaultPreVote)>
690bool
692 NetworkHistory& history,
693 NodeID const& myId,
694 std::size_t expect,
695 PreVote const& pre = defaultPreVote)
696{
697 NegativeUNLVote vote(myId, history.env.journal);
698 pre(vote);
699 auto txSet = std::make_shared<SHAMap>(
701 vote.doVoting(
702 history.lastLedger(), history.UNLKeySet, history.validations, txSet);
703 return countTx(txSet) == expect;
704}
705
710{
711 void
713 {
714 testcase("Create UNLModify Tx");
715 jtx::Env env(*this);
716
717 NodeID myId(0xA0);
718 NegativeUNLVote vote(myId, env.journal);
719
720 // one add, one remove
721 auto txSet = std::make_shared<SHAMap>(
723 PublicKey toDisableKey(
725 PublicKey toReEnableKey(
727 LedgerIndex seq(1234);
728 BEAST_EXPECT(countTx(txSet) == 0);
729 vote.addTx(seq, toDisableKey, NegativeUNLVote::ToDisable, txSet);
730 BEAST_EXPECT(countTx(txSet) == 1);
731 vote.addTx(seq, toReEnableKey, NegativeUNLVote::ToReEnable, txSet);
732 BEAST_EXPECT(countTx(txSet) == 2);
733 // content of a tx is implicitly tested after applied to a ledger
734 // in later test cases
735 }
736
737 void
739 {
740 testcase("Pick One Candidate");
741 jtx::Env env(*this);
742
743 NodeID myId(0xA0);
744 NegativeUNLVote vote(myId, env.journal);
745
746 uint256 pad_0(0);
747 uint256 pad_f = ~pad_0;
748 NodeID n_1(1);
749 NodeID n_2(2);
750 NodeID n_3(3);
751 std::vector<NodeID> candidates({n_1});
752 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
753 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_1);
754 candidates.emplace_back(2);
755 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
756 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_2);
757 candidates.emplace_back(3);
758 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
759 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_3);
760 }
761
762 void
764 {
765 testcase("Build Score Table");
766 /*
767 * 1. no skip list
768 * 2. short skip list
769 * 3. local node not enough history
770 * 4. a node double validated some seq
771 * 5. local node had enough validations but on a wrong chain
772 * 6. a good case, long enough history and perfect scores
773 */
774 {
775 // 1. no skip list
776 NetworkHistory history = {*this, {10, 0, false, false, 1}};
777 BEAST_EXPECT(history.goodHistory);
778 if (history.goodHistory)
779 {
780 NegativeUNLVote vote(
781 history.UNLNodeIDs[3], history.env.journal);
782 BEAST_EXPECT(!vote.buildScoreTable(
783 history.lastLedger(),
784 history.UNLNodeIDSet,
785 history.validations));
786 }
787 }
788
789 {
790 // 2. short skip list
791 NetworkHistory history = {*this, {10, 0, false, false, 256 / 2}};
792 BEAST_EXPECT(history.goodHistory);
793 if (history.goodHistory)
794 {
795 NegativeUNLVote vote(
796 history.UNLNodeIDs[3], history.env.journal);
797 BEAST_EXPECT(!vote.buildScoreTable(
798 history.lastLedger(),
799 history.UNLNodeIDSet,
800 history.validations));
801 }
802 }
803
804 {
805 // 3. local node not enough history
806 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
807 BEAST_EXPECT(history.goodHistory);
808 if (history.goodHistory)
809 {
810 NodeID myId = history.UNLNodeIDs[3];
813 std::size_t idx) -> bool {
814 // skip half my validations.
815 return !(
816 history.UNLNodeIDs[idx] == myId &&
817 l->seq() % 2 == 0);
818 });
819 NegativeUNLVote vote(myId, history.env.journal);
820 BEAST_EXPECT(!vote.buildScoreTable(
821 history.lastLedger(),
822 history.UNLNodeIDSet,
823 history.validations));
824 }
825 }
826
827 {
828 // 4. a node double validated some seq
829 // 5. local node had enough validations but on a wrong chain
830 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
831 // We need two chains for these tests
832 bool wrongChainSuccess = history.goodHistory;
833 BEAST_EXPECT(wrongChainSuccess);
835 std::move(history.history);
836 // Create a new chain and use it as the one that majority of nodes
837 // follow
838 history.createLedgerHistory();
839 BEAST_EXPECT(history.goodHistory);
840
841 if (history.goodHistory && wrongChainSuccess)
842 {
843 NodeID myId = history.UNLNodeIDs[3];
844 NodeID badNode = history.UNLNodeIDs[4];
847 std::size_t idx) -> bool {
848 // everyone but me
849 return !(history.UNLNodeIDs[idx] == myId);
850 });
851
852 // local node validate wrong chain
853 // a node double validates
854 for (auto& l : wrongChain)
855 {
856 RCLValidation v1(history.createSTVal(l, myId));
857 history.validations.add(myId, v1);
858 RCLValidation v2(history.createSTVal(l, badNode));
859 history.validations.add(badNode, v2);
860 }
861
862 NegativeUNLVote vote(myId, history.env.journal);
863
864 // local node still on wrong chain, can build a scoreTable,
865 // but all other nodes' scores are zero
866 auto scoreTable = vote.buildScoreTable(
867 wrongChain.back(),
868 history.UNLNodeIDSet,
869 history.validations);
870 BEAST_EXPECT(scoreTable);
871 if (scoreTable)
872 {
873 for (auto const& [n, score] : *scoreTable)
874 {
875 if (n == myId)
876 BEAST_EXPECT(score == 256);
877 else
878 BEAST_EXPECT(score == 0);
879 }
880 }
881
882 // if local node switched to right history, but cannot build
883 // scoreTable because not enough local validations
884 BEAST_EXPECT(!vote.buildScoreTable(
885 history.lastLedger(),
886 history.UNLNodeIDSet,
887 history.validations));
888 }
889 }
890
891 {
892 // 6. a good case
893 NetworkHistory history = {*this, {10, 0, false, false, 256 + 1}};
894 BEAST_EXPECT(history.goodHistory);
895 if (history.goodHistory)
896 {
899 std::size_t idx) -> bool { return true; });
900 NegativeUNLVote vote(
901 history.UNLNodeIDs[3], history.env.journal);
902 auto scoreTable = vote.buildScoreTable(
903 history.lastLedger(),
904 history.UNLNodeIDSet,
905 history.validations);
906 BEAST_EXPECT(scoreTable);
907 if (scoreTable)
908 {
909 for (auto const& [_, score] : *scoreTable)
910 {
911 (void)_;
912 BEAST_EXPECT(score == 256);
913 }
914 }
915 }
916 }
917 }
918
931 bool
933 NegativeUNLVote& vote,
934 hash_set<NodeID> const& unl,
935 hash_set<NodeID> const& negUnl,
936 hash_map<NodeID, std::uint32_t> const& scoreTable,
937 std::size_t numDisable,
938 std::size_t numReEnable)
939 {
940 auto [disableCandidates, reEnableCandidates] =
941 vote.findAllCandidates(unl, negUnl, scoreTable);
942 bool rightDisable = disableCandidates.size() == numDisable;
943 bool rightReEnable = reEnableCandidates.size() == numReEnable;
944 return rightDisable && rightReEnable;
945 };
946
947 void
949 {
950 testcase("Find All Candidates");
951 /*
952 * -- unl size: 35
953 * -- negUnl size: 3
954 *
955 * 0. all good scores
956 * 1. all bad scores
957 * 2. all between watermarks
958 * 3. 2 good scorers in negUnl
959 * 4. 2 bad scorers not in negUnl
960 * 5. 2 in negUnl but not in unl, have a remove candidate from score
961 * table
962 * 6. 2 in negUnl but not in unl, no remove candidate from score table
963 * 7. 2 new validators have good scores, already in negUnl
964 * 8. 2 new validators have bad scores, not in negUnl
965 * 9. expired the new validators have bad scores, not in negUnl
966 */
967 NetworkHistory history = {*this, {35, 0, false, false, 0}};
968
969 hash_set<NodeID> negUnl_012;
970 for (std::uint32_t i = 0; i < 3; ++i)
971 negUnl_012.insert(history.UNLNodeIDs[i]);
972
973 // build a good scoreTable to use, or copy and modify
974 hash_map<NodeID, std::uint32_t> goodScoreTable;
975 for (auto const& n : history.UNLNodeIDs)
976 goodScoreTable[n] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
977
978 NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal);
979
980 {
981 // all good scores
982 BEAST_EXPECT(checkCandidateSizes(
983 vote, history.UNLNodeIDSet, negUnl_012, goodScoreTable, 0, 3));
984 }
985 {
986 // all bad scores
988 for (auto& n : history.UNLNodeIDs)
990 BEAST_EXPECT(checkCandidateSizes(
991 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 35 - 3, 0));
992 }
993 {
994 // all between watermarks
996 for (auto& n : history.UNLNodeIDs)
998 BEAST_EXPECT(checkCandidateSizes(
999 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 0));
1000 }
1001
1002 {
1003 // 2 good scorers in negUnl
1004 auto scoreTable = goodScoreTable;
1005 scoreTable[*negUnl_012.begin()] =
1007 BEAST_EXPECT(checkCandidateSizes(
1008 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 2));
1009 }
1010
1011 {
1012 // 2 bad scorers not in negUnl
1013 auto scoreTable = goodScoreTable;
1014 scoreTable[history.UNLNodeIDs[11]] =
1016 scoreTable[history.UNLNodeIDs[12]] =
1018 BEAST_EXPECT(checkCandidateSizes(
1019 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 2, 3));
1020 }
1021
1022 {
1023 // 2 in negUnl but not in unl, have a remove candidate from score
1024 // table
1025 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1026 UNL_temp.erase(history.UNLNodeIDs[0]);
1027 UNL_temp.erase(history.UNLNodeIDs[1]);
1028 BEAST_EXPECT(checkCandidateSizes(
1029 vote, UNL_temp, negUnl_012, goodScoreTable, 0, 3));
1030 }
1031
1032 {
1033 // 2 in negUnl but not in unl, no remove candidate from score table
1034 auto scoreTable = goodScoreTable;
1035 scoreTable.erase(history.UNLNodeIDs[0]);
1036 scoreTable.erase(history.UNLNodeIDs[1]);
1037 scoreTable[history.UNLNodeIDs[2]] =
1039 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1040 UNL_temp.erase(history.UNLNodeIDs[0]);
1041 UNL_temp.erase(history.UNLNodeIDs[1]);
1042 BEAST_EXPECT(checkCandidateSizes(
1043 vote, UNL_temp, negUnl_012, scoreTable, 0, 2));
1044 }
1045
1046 {
1047 // 2 new validators
1048 NodeID new_1(0xbead);
1049 NodeID new_2(0xbeef);
1050 hash_set<NodeID> nowTrusted = {new_1, new_2};
1051 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1052 UNL_temp.insert(new_1);
1053 UNL_temp.insert(new_2);
1054 vote.newValidators(256, nowTrusted);
1055 {
1056 // 2 new validators have good scores, already in negUnl
1057 auto scoreTable = goodScoreTable;
1058 scoreTable[new_1] =
1060 scoreTable[new_2] =
1062 hash_set<NodeID> negUnl_temp = negUnl_012;
1063 negUnl_temp.insert(new_1);
1064 negUnl_temp.insert(new_2);
1065 BEAST_EXPECT(checkCandidateSizes(
1066 vote, UNL_temp, negUnl_temp, scoreTable, 0, 3 + 2));
1067 }
1068 {
1069 // 2 new validators have bad scores, not in negUnl
1070 auto scoreTable = goodScoreTable;
1071 scoreTable[new_1] = 0;
1072 scoreTable[new_2] = 0;
1073 BEAST_EXPECT(checkCandidateSizes(
1074 vote, UNL_temp, negUnl_012, scoreTable, 0, 3));
1075 }
1076 {
1077 // expired the new validators have bad scores, not in negUnl
1078 vote.purgeNewValidators(
1080 auto scoreTable = goodScoreTable;
1081 scoreTable[new_1] = 0;
1082 scoreTable[new_2] = 0;
1083 BEAST_EXPECT(checkCandidateSizes(
1084 vote, UNL_temp, negUnl_012, scoreTable, 2, 3));
1085 }
1086 }
1087 }
1088
1089 void
1091 {
1092 testcase("Find All Candidates Combination");
1093 /*
1094 * == combination 1:
1095 * -- unl size: 34, 35, 80
1096 * -- nUnl size: 0, 50%, all
1097 * -- score pattern: all 0, all negativeUNLLowWaterMark & +1 & -1, all
1098 * negativeUNLHighWaterMark & +1 & -1, all 100%
1099 *
1100 * == combination 2:
1101 * -- unl size: 34, 35, 80
1102 * -- negativeUNL size: 0, all
1103 * -- nUnl size: one on, one off, one on, one off,
1104 * -- score pattern: 2*(negativeUNLLowWaterMark, +1, -1) &
1105 * 2*(negativeUNLHighWaterMark, +1, -1) & rest
1106 * negativeUNLMinLocalValsToVote
1107 */
1108
1109 jtx::Env env(*this);
1110
1111 NodeID myId(0xA0);
1112 NegativeUNLVote vote(myId, env.journal);
1113
1114 std::array<std::uint32_t, 3> unlSizes = {34, 35, 80};
1115 std::array<std::uint32_t, 3> nUnlPercent = {0, 50, 100};
1117 0,
1125
1126 //== combination 1:
1127 {
1128 auto fillScoreTable =
1129 [&](std::uint32_t unl_size,
1130 std::uint32_t nUnl_size,
1131 std::uint32_t score,
1132 hash_set<NodeID>& unl,
1133 hash_set<NodeID>& negUnl,
1134 hash_map<NodeID, std::uint32_t>& scoreTable) {
1135 std::vector<NodeID> nodeIDs;
1137 for (auto const& k : keys)
1138 {
1139 nodeIDs.emplace_back(calcNodeID(k));
1140 unl.emplace(nodeIDs.back());
1141 scoreTable[nodeIDs.back()] = score;
1142 }
1143 for (std::uint32_t i = 0; i < nUnl_size; ++i)
1144 negUnl.insert(nodeIDs[i]);
1145 };
1146
1147 for (auto us : unlSizes)
1148 {
1149 for (auto np : nUnlPercent)
1150 {
1151 for (auto score : scores)
1152 {
1153 hash_set<NodeID> unl;
1154 hash_set<NodeID> negUnl;
1156 fillScoreTable(
1157 us, us * np / 100, score, unl, negUnl, scoreTable);
1158 BEAST_EXPECT(unl.size() == us);
1159 BEAST_EXPECT(negUnl.size() == us * np / 100);
1160 BEAST_EXPECT(scoreTable.size() == us);
1161
1162 std::size_t toDisable_expect = 0;
1163 std::size_t toReEnable_expect = 0;
1164 if (np == 0)
1165 {
1166 if (score <
1168 {
1169 toDisable_expect = us;
1170 }
1171 }
1172 else if (np == 50)
1173 {
1174 if (score >
1176 {
1177 toReEnable_expect = us * np / 100;
1178 }
1179 }
1180 else
1181 {
1182 if (score >
1184 {
1185 toReEnable_expect = us;
1186 }
1187 }
1188 BEAST_EXPECT(checkCandidateSizes(
1189 vote,
1190 unl,
1191 negUnl,
1192 scoreTable,
1193 toDisable_expect,
1194 toReEnable_expect));
1195 }
1196 }
1197 }
1198
1199 //== combination 2:
1200 {
1201 auto fillScoreTable =
1202 [&](std::uint32_t unl_size,
1203 std::uint32_t nUnl_percent,
1204 hash_set<NodeID>& unl,
1205 hash_set<NodeID>& negUnl,
1206 hash_map<NodeID, std::uint32_t>& scoreTable) {
1207 std::vector<NodeID> nodeIDs;
1209 createPublicKeys(unl_size);
1210 for (auto const& k : keys)
1211 {
1212 nodeIDs.emplace_back(calcNodeID(k));
1213 unl.emplace(nodeIDs.back());
1214 }
1215
1216 std::uint32_t nIdx = 0;
1217 for (auto score : scores)
1218 {
1219 scoreTable[nodeIDs[nIdx++]] = score;
1220 scoreTable[nodeIDs[nIdx++]] = score;
1221 }
1222 for (; nIdx < unl_size;)
1223 {
1224 scoreTable[nodeIDs[nIdx++]] = scores.back();
1225 }
1226
1227 if (nUnl_percent == 100)
1228 {
1229 negUnl = unl;
1230 }
1231 else if (nUnl_percent == 50)
1232 {
1233 for (std::uint32_t i = 1; i < unl_size; i += 2)
1234 negUnl.insert(nodeIDs[i]);
1235 }
1236 };
1237
1238 for (auto us : unlSizes)
1239 {
1240 for (auto np : nUnlPercent)
1241 {
1242 hash_set<NodeID> unl;
1243 hash_set<NodeID> negUnl;
1245
1246 fillScoreTable(us, np, unl, negUnl, scoreTable);
1247 BEAST_EXPECT(unl.size() == us);
1248 BEAST_EXPECT(negUnl.size() == us * np / 100);
1249 BEAST_EXPECT(scoreTable.size() == us);
1250
1251 std::size_t toDisable_expect = 0;
1252 std::size_t toReEnable_expect = 0;
1253 if (np == 0)
1254 {
1255 toDisable_expect = 4;
1256 }
1257 else if (np == 50)
1258 {
1259 toReEnable_expect = negUnl.size() - 6;
1260 }
1261 else
1262 {
1263 toReEnable_expect = negUnl.size() - 12;
1264 }
1265 BEAST_EXPECT(checkCandidateSizes(
1266 vote,
1267 unl,
1268 negUnl,
1269 scoreTable,
1270 toDisable_expect,
1271 toReEnable_expect));
1272 }
1273 }
1274 }
1275 }
1276 }
1277
1278 void
1280 {
1281 testcase("New Validators");
1282 jtx::Env env(*this);
1283
1284 NodeID myId(0xA0);
1285 NegativeUNLVote vote(myId, env.journal);
1286
1287 // test cases:
1288 // newValidators_ of the NegativeUNLVote empty, add one
1289 // add a new one and one already added
1290 // add a new one and some already added
1291 // purge and see some are expired
1292
1293 NodeID n1(0xA1);
1294 NodeID n2(0xA2);
1295 NodeID n3(0xA3);
1296
1297 vote.newValidators(2, {n1});
1298 BEAST_EXPECT(vote.newValidators_.size() == 1);
1299 if (vote.newValidators_.size() == 1)
1300 {
1301 BEAST_EXPECT(vote.newValidators_.begin()->first == n1);
1302 BEAST_EXPECT(vote.newValidators_.begin()->second == 2);
1303 }
1304
1305 vote.newValidators(3, {n1, n2});
1306 BEAST_EXPECT(vote.newValidators_.size() == 2);
1307 if (vote.newValidators_.size() == 2)
1308 {
1309 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1310 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1311 }
1312
1313 vote.newValidators(
1315 BEAST_EXPECT(vote.newValidators_.size() == 3);
1316 if (vote.newValidators_.size() == 3)
1317 {
1318 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1319 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1320 BEAST_EXPECT(
1321 vote.newValidators_[n3] ==
1323 }
1324
1326 BEAST_EXPECT(vote.newValidators_.size() == 3);
1328 BEAST_EXPECT(vote.newValidators_.size() == 2);
1330 BEAST_EXPECT(vote.newValidators_.size() == 1);
1331 BEAST_EXPECT(vote.newValidators_.begin()->first == n3);
1332 BEAST_EXPECT(
1333 vote.newValidators_.begin()->second ==
1335 }
1336
1337 void
1347};
1348
1355{
1356 void
1358 {
1359 testcase("Build Score Table Combination");
1360 /*
1361 * local node good history, correct scores:
1362 * == combination:
1363 * -- unl size: 10, 34, 35, 50
1364 * -- score pattern: all 0, all 50%, all 100%, two 0% two 50% rest 100%
1365 */
1366 std::array<std::uint32_t, 4> unlSizes = {10, 34, 35, 50};
1367 std::array<std::array<std::uint32_t, 3>, 4> scorePattern = {
1368 {{{0, 0, 0}}, {{50, 50, 50}}, {{100, 100, 100}}, {{0, 50, 100}}}};
1369
1370 for (auto unlSize : unlSizes)
1371 {
1372 for (std::uint32_t sp = 0; sp < 4; ++sp)
1373 {
1374 NetworkHistory history = {
1375 *this, {unlSize, 0, false, false, 256 + 2}};
1376 BEAST_EXPECT(history.goodHistory);
1377 if (history.goodHistory)
1378 {
1379 NodeID myId = history.UNLNodeIDs[3];
1381 [&](std::shared_ptr<Ledger const> const& l,
1382 std::size_t idx) -> bool {
1383 std::size_t k;
1384 if (idx < 2)
1385 k = 0;
1386 else if (idx < 4)
1387 k = 1;
1388 else
1389 k = 2;
1390
1391 bool add_50 =
1392 scorePattern[sp][k] == 50 && l->seq() % 2 == 0;
1393 bool add_100 = scorePattern[sp][k] == 100;
1394 bool add_me = history.UNLNodeIDs[idx] == myId;
1395 return add_50 || add_100 || add_me;
1396 });
1397
1398 NegativeUNLVote vote(myId, history.env.journal);
1399 auto scoreTable = vote.buildScoreTable(
1400 history.lastLedger(),
1401 history.UNLNodeIDSet,
1402 history.validations);
1403 BEAST_EXPECT(scoreTable);
1404 if (scoreTable)
1405 {
1406 std::uint32_t i = 0; // looping unl
1407 auto checkScores = [&](std::uint32_t score,
1408 std::uint32_t k) -> bool {
1409 if (history.UNLNodeIDs[i] == myId)
1410 return score == 256;
1411 if (scorePattern[sp][k] == 0)
1412 return score == 0;
1413 if (scorePattern[sp][k] == 50)
1414 return score == 256 / 2;
1415 if (scorePattern[sp][k] == 100)
1416 return score == 256;
1417 else
1418 return false;
1419 };
1420 for (; i < 2; ++i)
1421 {
1422 BEAST_EXPECT(checkScores(
1423 (*scoreTable)[history.UNLNodeIDs[i]], 0));
1424 }
1425 for (; i < 4; ++i)
1426 {
1427 BEAST_EXPECT(checkScores(
1428 (*scoreTable)[history.UNLNodeIDs[i]], 1));
1429 }
1430 for (; i < unlSize; ++i)
1431 {
1432 BEAST_EXPECT(checkScores(
1433 (*scoreTable)[history.UNLNodeIDs[i]], 2));
1434 }
1435 }
1436 }
1437 }
1438 }
1439 }
1440
1441 void
1442 run() override
1443 {
1445 }
1446};
1447
1448/*
1449 * Test the doVoting function of NegativeUNLVote.
1450 * The test cases are split to 5 classes for parallel execution.
1451 *
1452 * Voting tests: (use hasToDisable and hasToReEnable in some of the cases)
1453 *
1454 * == all good score, nUnl empty
1455 * -- txSet.size = 0
1456 * == all good score, nUnl not empty (use hasToDisable)
1457 * -- txSet.size = 1
1458 *
1459 * == 2 nodes offline, nUnl empty (use hasToReEnable)
1460 * -- txSet.size = 1
1461 * == 2 nodes offline, in nUnl
1462 * -- txSet.size = 0
1463 *
1464 * == 2 nodes offline, not in nUnl, but maxListed
1465 * -- txSet.size = 0
1466 *
1467 * == 2 nodes offline including me, not in nUnl
1468 * -- txSet.size = 0
1469 * == 2 nodes offline, not in negativeUNL, but I'm not a validator
1470 * -- txSet.size = 0
1471 * == 2 in nUnl, but not in unl, no other remove candidates
1472 * -- txSet.size = 1
1473 *
1474 * == 2 new validators have bad scores
1475 * -- txSet.size = 0
1476 * == 2 expired new validators have bad scores
1477 * -- txSet.size = 1
1478 */
1479
1481{
1482 void
1484 {
1485 testcase("Do Voting");
1486
1487 {
1488 //== all good score, negativeUNL empty
1489 //-- txSet.size = 0
1490 NetworkHistory history = {*this, {51, 0, false, false, {}}};
1491 BEAST_EXPECT(history.goodHistory);
1492 if (history.goodHistory)
1493 {
1495 [&](std::shared_ptr<Ledger const> const& l,
1496 std::size_t idx) -> bool { return true; });
1497 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1498 }
1499 }
1500
1501 {
1502 // all good score, negativeUNL not empty (use hasToDisable)
1503 //-- txSet.size = 1
1504 NetworkHistory history = {*this, {37, 0, true, false, {}}};
1505 BEAST_EXPECT(history.goodHistory);
1506 if (history.goodHistory)
1507 {
1509 [&](std::shared_ptr<Ledger const> const& l,
1510 std::size_t idx) -> bool { return true; });
1511 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 1));
1512 }
1513 }
1514 }
1515
1516 void
1517 run() override
1518 {
1519 testDoVoting();
1520 }
1521};
1522
1524{
1525 void
1527 {
1528 testcase("Do Voting");
1529
1530 {
1531 //== 2 nodes offline, negativeUNL empty (use hasToReEnable)
1532 //-- txSet.size = 1
1533 NetworkHistory history = {*this, {29, 1, false, true, {}}};
1534 BEAST_EXPECT(history.goodHistory);
1535 if (history.goodHistory)
1536 {
1538 [&](std::shared_ptr<Ledger const> const& l,
1539 std::size_t idx) -> bool {
1540 // skip node 0 and node 1
1541 return idx > 1;
1542 });
1543 BEAST_EXPECT(
1544 voteAndCheck(history, history.UNLNodeIDs.back(), 1));
1545 }
1546 }
1547
1548 {
1549 // 2 nodes offline, in negativeUNL
1550 //-- txSet.size = 0
1551 NetworkHistory history = {*this, {30, 1, true, false, {}}};
1552 BEAST_EXPECT(history.goodHistory);
1553 if (history.goodHistory)
1554 {
1555 NodeID n1 =
1556 calcNodeID(*history.lastLedger()->negativeUNL().begin());
1557 NodeID n2 =
1558 calcNodeID(*history.lastLedger()->validatorToDisable());
1560 [&](std::shared_ptr<Ledger const> const& l,
1561 std::size_t idx) -> bool {
1562 // skip node 0 and node 1
1563 return history.UNLNodeIDs[idx] != n1 &&
1564 history.UNLNodeIDs[idx] != n2;
1565 });
1566 BEAST_EXPECT(
1567 voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1568 }
1569 }
1570 }
1571
1572 void
1573 run() override
1574 {
1575 testDoVoting();
1576 }
1577};
1578
1580{
1581 void
1583 {
1584 testcase("Do Voting");
1585
1586 {
1587 // 2 nodes offline, not in negativeUNL, but maxListed
1588 //-- txSet.size = 0
1589 NetworkHistory history = {*this, {32, 8, true, true, {}}};
1590 BEAST_EXPECT(history.goodHistory);
1591 if (history.goodHistory)
1592 {
1594 [&](std::shared_ptr<Ledger const> const& l,
1595 std::size_t idx) -> bool {
1596 // skip node 0 ~ 10
1597 return idx > 10;
1598 });
1599 BEAST_EXPECT(
1600 voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1601 }
1602 }
1603 }
1604
1605 void
1606 run() override
1607 {
1608 testDoVoting();
1609 }
1610};
1611
1613{
1614 void
1616 {
1617 testcase("Do Voting");
1618
1619 {
1620 //== 2 nodes offline including me, not in negativeUNL
1621 //-- txSet.size = 0
1622 NetworkHistory history = {*this, {35, 0, false, false, {}}};
1623 BEAST_EXPECT(history.goodHistory);
1624 if (history.goodHistory)
1625 {
1627 [&](std::shared_ptr<Ledger const> const& l,
1628 std::size_t idx) -> bool { return idx > 1; });
1629 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1630 }
1631 }
1632
1633 {
1634 // 2 nodes offline, not in negativeUNL, but I'm not a validator
1635 //-- txSet.size = 0
1636 NetworkHistory history = {*this, {40, 0, false, false, {}}};
1637 BEAST_EXPECT(history.goodHistory);
1638 if (history.goodHistory)
1639 {
1641 [&](std::shared_ptr<Ledger const> const& l,
1642 std::size_t idx) -> bool { return idx > 1; });
1643 BEAST_EXPECT(voteAndCheck(history, NodeID(0xdeadbeef), 0));
1644 }
1645 }
1646
1647 {
1648 //== 2 in negativeUNL, but not in unl, no other remove candidates
1649 //-- txSet.size = 1
1650 NetworkHistory history = {*this, {25, 2, false, false, {}}};
1651 BEAST_EXPECT(history.goodHistory);
1652 if (history.goodHistory)
1653 {
1655 [&](std::shared_ptr<Ledger const> const& l,
1656 std::size_t idx) -> bool { return idx > 1; });
1657 BEAST_EXPECT(voteAndCheck(
1658 history,
1659 history.UNLNodeIDs.back(),
1660 1,
1661 [&](NegativeUNLVote& vote) {
1662 history.UNLKeySet.erase(history.UNLKeys[0]);
1663 history.UNLKeySet.erase(history.UNLKeys[1]);
1664 }));
1665 }
1666 }
1667 }
1668
1669 void
1670 run() override
1671 {
1672 testDoVoting();
1673 }
1674};
1675
1677{
1678 void
1680 {
1681 testcase("Do Voting");
1682
1683 {
1684 //== 2 new validators have bad scores
1685 //-- txSet.size = 0
1686 NetworkHistory history = {*this, {15, 0, false, false, {}}};
1687 BEAST_EXPECT(history.goodHistory);
1688 if (history.goodHistory)
1689 {
1691 [&](std::shared_ptr<Ledger const> const& l,
1692 std::size_t idx) -> bool { return true; });
1693 BEAST_EXPECT(voteAndCheck(
1694 history,
1695 history.UNLNodeIDs[0],
1696 0,
1697 [&](NegativeUNLVote& vote) {
1698 auto extra_key_1 =
1699 randomKeyPair(KeyType::ed25519).first;
1700 auto extra_key_2 =
1701 randomKeyPair(KeyType::ed25519).first;
1702 history.UNLKeySet.insert(extra_key_1);
1703 history.UNLKeySet.insert(extra_key_2);
1704 hash_set<NodeID> nowTrusted;
1705 nowTrusted.insert(calcNodeID(extra_key_1));
1706 nowTrusted.insert(calcNodeID(extra_key_2));
1707 vote.newValidators(
1708 history.lastLedger()->seq(), nowTrusted);
1709 }));
1710 }
1711 }
1712
1713 {
1714 //== 2 expired new validators have bad scores
1715 //-- txSet.size = 1
1716 NetworkHistory history = {
1717 *this,
1718 {21,
1719 0,
1720 false,
1721 false,
1723 BEAST_EXPECT(history.goodHistory);
1724 if (history.goodHistory)
1725 {
1727 [&](std::shared_ptr<Ledger const> const& l,
1728 std::size_t idx) -> bool { return true; });
1729 BEAST_EXPECT(voteAndCheck(
1730 history,
1731 history.UNLNodeIDs[0],
1732 1,
1733 [&](NegativeUNLVote& vote) {
1734 auto extra_key_1 =
1735 randomKeyPair(KeyType::ed25519).first;
1736 auto extra_key_2 =
1737 randomKeyPair(KeyType::ed25519).first;
1738 history.UNLKeySet.insert(extra_key_1);
1739 history.UNLKeySet.insert(extra_key_2);
1740 hash_set<NodeID> nowTrusted;
1741 nowTrusted.insert(calcNodeID(extra_key_1));
1742 nowTrusted.insert(calcNodeID(extra_key_2));
1743 vote.newValidators(256, nowTrusted);
1744 }));
1745 }
1746 }
1747 }
1748
1749 void
1750 run() override
1751 {
1752 testDoVoting();
1753 }
1754};
1755
1757{
1758 void
1760 {
1761 testcase("Filter Validations");
1762 jtx::Env env(*this);
1763 auto l = std::make_shared<Ledger>(
1765 env.app().config(),
1767 env.app().getNodeFamily());
1768
1769 auto createSTVal = [&](std::pair<PublicKey, SecretKey> const& keys) {
1771 env.app().timeKeeper().now(),
1772 keys.first,
1773 keys.second,
1774 calcNodeID(keys.first),
1775 [&](STValidation& v) {
1776 v.setFieldH256(sfLedgerHash, l->header().hash);
1777 v.setFieldU32(sfLedgerSequence, l->seq());
1778 v.setFlag(vfFullValidation);
1779 });
1780 };
1781
1782 // create keys and validations
1783 std::uint32_t numNodes = 10;
1784 std::uint32_t negUnlSize = 3;
1786 hash_set<NodeID> activeValidators;
1787 hash_set<PublicKey> nUnlKeys;
1789 for (int i = 0; i < numNodes; ++i)
1790 {
1791 auto keyPair = randomKeyPair(KeyType::secp256k1);
1792 vals.emplace_back(createSTVal(keyPair));
1793 cfgKeys.push_back(toBase58(TokenType::NodePublic, keyPair.first));
1794 activeValidators.emplace(calcNodeID(keyPair.first));
1795 if (i < negUnlSize)
1796 {
1797 nUnlKeys.insert(keyPair.first);
1798 }
1799 }
1800
1801 // setup the ValidatorList
1802 auto& validators = env.app().validators();
1803 auto& local = *nUnlKeys.begin();
1804 std::vector<std::string> cfgPublishers;
1805 validators.load(local, cfgKeys, cfgPublishers);
1806 validators.updateTrusted(
1807 activeValidators,
1808 env.timeKeeper().now(),
1809 env.app().getOPs(),
1810 env.app().overlay(),
1811 env.app().getHashRouter());
1812 BEAST_EXPECT(validators.getTrustedMasterKeys().size() == numNodes);
1813 validators.setNegativeUNL(nUnlKeys);
1814 BEAST_EXPECT(validators.getNegativeUNL().size() == negUnlSize);
1815
1816 // test the filter
1817 BEAST_EXPECT(vals.size() == numNodes);
1818 vals = validators.negativeUNLFilter(std::move(vals));
1819 BEAST_EXPECT(vals.size() == numNodes - negUnlSize);
1820 }
1821
1822 void
1823 run() override
1824 {
1826 }
1827};
1828
1829BEAST_DEFINE_TESTSUITE(NegativeUNL, consensus, xrpl);
1830
1831BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, xrpl);
1832BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, xrpl);
1833BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteGoodScore, consensus, xrpl, 1);
1834BEAST_DEFINE_TESTSUITE(NegativeUNLVoteOffline, consensus, xrpl);
1835BEAST_DEFINE_TESTSUITE(NegativeUNLVoteMaxListed, consensus, xrpl);
1836BEAST_DEFINE_TESTSUITE_PRIO(
1837 NegativeUNLVoteRetiredValidator,
1838 consensus,
1839 ripple,
1840 1);
1841BEAST_DEFINE_TESTSUITE(NegativeUNLVoteNewValidator, consensus, xrpl);
1842BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, xrpl);
1843
1847bool
1850 size_t size,
1851 bool hasToDisable,
1852 bool hasToReEnable)
1853{
1854 bool sameSize = l->negativeUNL().size() == size;
1855 bool sameToDisable =
1856 (l->validatorToDisable() != std::nullopt) == hasToDisable;
1857 bool sameToReEnable =
1858 (l->validatorToReEnable() != std::nullopt) == hasToReEnable;
1859
1860 return sameSize && sameToDisable && sameToReEnable;
1861}
1862
1863bool
1864applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
1865{
1866 auto const res =
1867 apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
1868 if (pass)
1869 return res.ter == tesSUCCESS;
1870 else
1871 return res.ter == tefFAILURE || res.ter == temDISABLED;
1872}
1873
1874bool
1878{
1879 auto sle = l->read(keylet::negativeUNL());
1880 if (!sle)
1881 return false;
1882 if (!sle->isFieldPresent(sfDisabledValidators))
1883 return false;
1884
1885 auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
1886 if (nUnlData.size() != nUnlLedgerSeq.size())
1887 return false;
1888
1889 for (auto const& n : nUnlData)
1890 {
1891 if (!n.isFieldPresent(sfFirstLedgerSequence) ||
1892 !n.isFieldPresent(sfPublicKey))
1893 return false;
1894
1895 auto seq = n.getFieldU32(sfFirstLedgerSequence);
1896 auto d = n.getFieldVL(sfPublicKey);
1897 auto s = makeSlice(d);
1898 if (!publicKeyType(s))
1899 return false;
1900 PublicKey pk(s);
1901 auto it = nUnlLedgerSeq.find(pk);
1902 if (it == nUnlLedgerSeq.end())
1903 return false;
1904 if (it->second != seq)
1905 return false;
1906 nUnlLedgerSeq.erase(it);
1907 }
1908 return nUnlLedgerSeq.size() == 0;
1909}
1910
1913{
1914 std::size_t count = 0;
1915 for (auto i = txSet->begin(); i != txSet->end(); ++i)
1916 {
1917 ++count;
1918 }
1919 return count;
1920};
1921
1924{
1926 std::size_t ss = 33;
1928 data[0] = 0xED;
1929 for (int i = 0; i < n; ++i)
1930 {
1931 data[1]++;
1932 Slice s(data.data(), ss);
1933 keys.emplace_back(s);
1934 }
1935 return keys;
1936}
1937
1938STTx
1939createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
1940{
1941 auto fill = [&](auto& obj) {
1942 obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
1943 obj.setFieldU32(sfLedgerSequence, seq);
1944 obj.setFieldVL(sfUNLModifyValidator, txKey);
1945 };
1946 return STTx(ttUNL_MODIFY, fill);
1947}
1948
1949} // namespace test
1950} // namespace xrpl
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
virtual HashRouter & getHashRouter()=0
virtual Config & config()=0
virtual Family & getNodeFamily()=0
virtual TimeKeeper & timeKeeper()=0
virtual Overlay & overlay()=0
virtual ValidatorList & validators()=0
virtual NetworkOPs & getOPs()=0
Manager to create NegativeUNL votes.
static constexpr size_t negativeUNLHighWaterMark
An unreliable validator must have more than negativeUNLHighWaterMark validations in the last flag led...
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
static constexpr size_t negativeUNLMinLocalValsToVote
The minimum number of validations of the local node for it to participate in the voting.
NodeID choose(uint256 const &randomPadData, std::vector< NodeID > const &candidates)
Pick one candidate from a vector of candidates.
void purgeNewValidators(LedgerIndex seq)
Purge validators that are not new anymore.
static constexpr size_t negativeUNLLowWaterMark
A validator is considered unreliable if its validations is less than negativeUNLLowWaterMark in the l...
std::optional< hash_map< NodeID, std::uint32_t > > buildScoreTable(std::shared_ptr< Ledger const > const &prevLedger, hash_set< NodeID > const &unl, RCLValidations &validations)
Build a reliability measurement score table of validators' validation messages in the last flag ledge...
static constexpr size_t newValidatorDisableSkip
We don't want to disable new validators immediately after adding them.
void doVoting(std::shared_ptr< Ledger const > const &prevLedger, hash_set< PublicKey > const &unlKeys, RCLValidations &validations, std::shared_ptr< SHAMap > const &initialSet)
Cast our local vote on the NegativeUNL candidates.
hash_map< NodeID, LedgerIndex > newValidators_
Candidates const findAllCandidates(hash_set< NodeID > const &unl, hash_set< NodeID > const &negUnl, hash_map< NodeID, std::uint32_t > const &scoreTable)
Process the score table and find all disabling and re-enabling candidates.
void addTx(LedgerIndex seq, PublicKey const &vp, NegativeUNLModify modify, std::shared_ptr< SHAMap > const &initialSet)
Add a ttUNL_MODIFY Tx to the transaction set.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:46
void apply(TxsRawView &to) const
Apply changes.
Definition OpenView.cpp:109
A public key.
Definition PublicKey.h:43
Wrapper over STValidation for generic Validation code.
An immutable linear range of bytes.
Definition Slice.h:27
time_point now() const override
Returns the current time, using the server's clock.
Definition TimeKeeper.h:45
time_point closeTime() const
Returns the predicted close time, in network time.
Definition TimeKeeper.h:57
ValStatus add(NodeID const &nodeID, Validation const &val)
Add a new validation.
time_point now() const override
Returns the current time.
Test the private member functions of NegativeUNLVote.
bool checkCandidateSizes(NegativeUNLVote &vote, hash_set< NodeID > const &unl, hash_set< NodeID > const &negUnl, hash_map< NodeID, std::uint32_t > const &scoreTable, std::size_t numDisable, std::size_t numReEnable)
Find all candidates and check if the number of candidates meets expectation.
Rest the build score table function of NegativeUNLVote.
void testNegativeUNL()
Test filling and applying ttUNL_MODIFY Tx, as well as ledger update:
void run() override
Runs the suite.
A transaction testing environment.
Definition Env.h:102
Application & app()
Definition Env.h:244
ManualTimeKeeper & timeKeeper()
Definition Env.h:256
beast::Journal const journal
Definition Env.h:143
T emplace_back(T... args)
T emplace(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T insert(T... args)
T is_same_v
Keylet const & negativeUNL() noexcept
The (fixed) index of the object containing the ledger negativeUNL.
Definition Indexes.cpp:212
auto const data
General field definitions, or fields used in multiple transaction namespaces.
FeatureBitset testable_amendments()
Definition Env.h:55
bool negUnlSizeTest(std::shared_ptr< Ledger const > const &l, size_t size, bool hasToDisable, bool hasToReEnable)
Test the size of the negative UNL in a ledger, also test if the ledger has ToDisable and/or ToReEnabl...
bool voteAndCheck(NetworkHistory &history, NodeID const &myId, std::size_t expect, PreVote const &pre=defaultPreVote)
Create a NegativeUNLVote object.
bool applyAndTestResult(jtx::Env &env, OpenView &view, STTx const &tx, bool pass)
Try to apply a ttUNL_MODIFY Tx, and test the apply result.
std::vector< PublicKey > createPublicKeys(std::size_t n)
Create fake public keys.
bool VerifyPubKeyAndSeq(std::shared_ptr< Ledger const > const &l, hash_map< PublicKey, std::uint32_t > nUnlLedgerSeq)
Verify the content of negative UNL entries (public key and ledger sequence) of a ledger.
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
std::size_t countTx(std::shared_ptr< SHAMap > const &txSet)
Count the number of Tx in a TxSet.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
base_uint< 160, detail::NodeIDTag > NodeID
NodeID is a 160-bit hash representing one node.
Definition UintTypes.h:40
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:128
@ tefFAILURE
Definition TER.h:147
create_genesis_t const create_genesis
Definition Ledger.cpp:32
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:255
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
SecretKey randomSecretKey()
Create a secret key using secure random numbers.
@ tapNONE
Definition ApplyView.h:12
@ temDISABLED
Definition TER.h:95
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:225
@ tesSUCCESS
Definition TER.h:226
T push_back(T... args)
T size(T... args)
Only reasonable parameters can be honored, e.g cannot hasToReEnable when nUNLSize == 0.
std::optional< int > numLedgers
if not specified, the number of ledgers in the history is calculated from negUNLSize,...
Utility class for creating validators and ledger history.
std::vector< NodeID > UNLNodeIDs
std::shared_ptr< STValidation > createSTVal(std::shared_ptr< Ledger const > const &ledger, NodeID const &v)
Create a validation.
void walkHistoryAndAddValidations(NeedValidation &&needVal)
Walk the ledger history and create validation messages for the ledgers.
std::shared_ptr< Ledger const > lastLedger() const
std::vector< PublicKey > UNLKeys
bool createLedgerHistory()
create ledger history and apply needed ttUNL_MODIFY tx at flag ledgers
hash_set< PublicKey > UNLKeySet
NetworkHistory(beast::unit_test::suite &suite, Parameter const &p)
Set the sequence number on a JTx.
Definition seq.h:15