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 ripple {
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_amemdment; // So we have different genesis ledgers
559 env.app().config(),
560 std::vector<uint256>{fake_amemdment++},
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->info().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->info().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, ripple);
1830
1831BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, ripple);
1832BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, ripple);
1833BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteGoodScore, consensus, ripple, 1);
1834BEAST_DEFINE_TESTSUITE(NegativeUNLVoteOffline, consensus, ripple);
1835BEAST_DEFINE_TESTSUITE(NegativeUNLVoteMaxListed, consensus, ripple);
1836BEAST_DEFINE_TESTSUITE_PRIO(
1837 NegativeUNLVoteRetiredValidator,
1838 consensus,
1839 ripple,
1840 1);
1841BEAST_DEFINE_TESTSUITE(NegativeUNLVoteNewValidator, consensus, ripple);
1842BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, ripple);
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 ripple
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 Config & config()=0
virtual Overlay & overlay()=0
virtual TimeKeeper & timeKeeper()=0
virtual NetworkOPs & getOPs()=0
virtual ValidatorList & validators()=0
virtual Family & getNodeFamily()=0
virtual HashRouter & getHashRouter()=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...
NodeID choose(uint256 const &randomPadData, std::vector< NodeID > const &candidates)
Pick one candidate from a vector of candidates.
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...
void purgeNewValidators(LedgerIndex seq)
Purge validators that are not new anymore.
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.
static constexpr size_t newValidatorDisableSkip
We don't want to disable new validators immediately after adding them.
static constexpr size_t negativeUNLLowWaterMark
A validator is considered unreliable if its validations is less than negativeUNLLowWaterMark in the l...
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_
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
void addTx(LedgerIndex seq, PublicKey const &vp, NegativeUNLModify modify, std::shared_ptr< SHAMap > const &initialSet)
Add a ttUNL_MODIFY Tx to the transaction set.
static constexpr size_t negativeUNLMinLocalValsToVote
The minimum number of validations of the local node for it to participate in the voting.
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 run() override
Runs the suite.
void testNegativeUNL()
Test filling and applying ttUNL_MODIFY Tx, as well as ledger update:
A transaction testing environment.
Definition Env.h:102
Application & app()
Definition Env.h:242
beast::Journal const journal
Definition Env.h:143
ManualTimeKeeper & timeKeeper()
Definition Env.h:254
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:211
auto const data
General field definitions, or fields used in multiple transaction namespaces.
FeatureBitset testable_amendments()
Definition Env.h:55
std::size_t countTx(std::shared_ptr< SHAMap > const &txSet)
Count the number of Tx in a TxSet.
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 ToDisalbe and/or ToReEnabl...
std::vector< PublicKey > createPublicKeys(std::size_t n)
Create fake public keys.
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
bool applyAndTestResult(jtx::Env &env, OpenView &view, STTx const &tx, bool pass)
Try to apply a ttUNL_MODIFY Tx, and test the apply result.
bool voteAndCheck(NetworkHistory &history, NodeID const &myId, std::size_t expect, PreVote const &pre=defaultPreVote)
Create a NegativeUNLVote object.
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.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:120
base_uint< 160, detail::NodeIDTag > NodeID
NodeID is a 160-bit hash representing one node.
Definition UintTypes.h:40
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
@ tefFAILURE
Definition TER.h:147
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
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
SecretKey randomSecretKey()
Create a secret key using secure random numbers.
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
@ tesSUCCESS
Definition TER.h:226
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:127
create_genesis_t const create_genesis
Definition Ledger.cpp:32
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
@ tapNONE
Definition ApplyView.h:12
@ temDISABLED
Definition TER.h:95
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::shared_ptr< Ledger const > lastLedger() const
void walkHistoryAndAddValidations(NeedValidation &&needVal)
Walk the ledger history and create validation messages for the ledgers.
NetworkHistory(beast::unit_test::suite &suite, Parameter const &p)
std::vector< NodeID > UNLNodeIDs
std::shared_ptr< STValidation > createSTVal(std::shared_ptr< Ledger const > const &ledger, NodeID const &v)
Create a validation.
std::vector< PublicKey > UNLKeys
bool createLedgerHistory()
create ledger history and apply needed ttUNL_MODIFY tx at flag ledgers
hash_set< PublicKey > UNLKeySet
Set the sequence number on a JTx.
Definition seq.h:15