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/misc/NegativeUNLVote.h>
5#include <xrpld/app/misc/ValidatorList.h>
6
7#include <xrpl/beast/unit_test.h>
8#include <xrpl/ledger/OpenView.h>
9#include <xrpl/tx/apply.h>
10
11namespace xrpl {
12namespace test {
13
14/*
15 * This file implements the following negative UNL related tests:
16 * -- test filling and applying ttUNL_MODIFY Tx and ledger update
17 * -- test the NegativeUNLVote class. The test cases are split to multiple
18 * test classes to allow parallel execution.
19 * -- test the negativeUNLFilter function
20 *
21 * Other negative UNL related tests such as ValidatorList and RPC related ones
22 * are put in their existing unit test files.
23 */
24
35bool
38 size_t size,
39 bool hasToDisable,
40 bool hasToReEnable);
41
51bool
52applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
53
63bool
66 hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq);
67
76
85
94STTx
95createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
96
98{
106 void
108 {
109 /*
110 * test cases:
111 *
112 * (1) the ledger after genesis
113 * -- cannot apply Disable Tx
114 * -- cannot apply ReEnable Tx
115 * -- nUNL empty
116 * -- no ToDisable
117 * -- no ToReEnable
118 *
119 * (2) a flag ledger
120 * -- apply an Disable Tx
121 * -- cannot apply the second Disable Tx
122 * -- cannot apply a ReEnable Tx
123 * -- nUNL empty
124 * -- has ToDisable with right nodeId
125 * -- no ToReEnable
126 * ++ extra test: first Disable Tx in ledger TxSet
127 *
128 * (3) ledgers before the next flag ledger
129 * -- nUNL empty
130 * -- has ToDisable with right nodeId
131 * -- no ToReEnable
132 *
133 * (4) next flag ledger
134 * -- nUNL size == 1, with right nodeId
135 * -- no ToDisable
136 * -- no ToReEnable
137 * -- cannot apply an Disable Tx with nodeId already in nUNL
138 * -- apply an Disable Tx with different nodeId
139 * -- cannot apply a ReEnable Tx with the same NodeId as Add
140 * -- cannot apply a ReEnable Tx with a NodeId not in nUNL
141 * -- apply a ReEnable Tx with a nodeId already in nUNL
142 * -- has ToDisable with right nodeId
143 * -- has ToReEnable with right nodeId
144 * -- nUNL size still 1, right nodeId
145 *
146 * (5) ledgers before the next flag ledger
147 * -- nUNL size == 1, right nodeId
148 * -- has ToDisable with right nodeId
149 * -- has ToReEnable with right nodeId
150 *
151 * (6) next flag ledger
152 * -- nUNL size == 1, different nodeId
153 * -- no ToDisable
154 * -- no ToReEnable
155 * -- apply an Disable Tx with different nodeId
156 * -- nUNL size still 1, right nodeId
157 * -- has ToDisable with right nodeId
158 * -- no ToReEnable
159 *
160 * (7) ledgers before the next flag ledger
161 * -- nUNL size still 1, right nodeId
162 * -- has ToDisable with right nodeId
163 * -- no ToReEnable
164 *
165 * (8) next flag ledger
166 * -- nUNL size == 2
167 * -- apply a ReEnable Tx
168 * -- cannot apply second ReEnable Tx, even with right nodeId
169 * -- cannot apply an Disable Tx with the same NodeId as Remove
170 * -- nUNL size == 2
171 * -- no ToDisable
172 * -- has ToReEnable with right nodeId
173 *
174 * (9) ledgers before the next flag ledger
175 * -- nUNL size == 2
176 * -- no ToDisable
177 * -- has ToReEnable with right nodeId
178 *
179 * (10) next flag ledger
180 * -- nUNL size == 1
181 * -- apply a ReEnable Tx
182 * -- nUNL size == 1
183 * -- no ToDisable
184 * -- has ToReEnable with right nodeId
185 *
186 * (11) ledgers before the next flag ledger
187 * -- nUNL size == 1
188 * -- no ToDisable
189 * -- has ToReEnable with right nodeId
190 *
191 * (12) next flag ledger
192 * -- nUNL size == 0
193 * -- no ToDisable
194 * -- no ToReEnable
195 *
196 * (13) ledgers before the next flag ledger
197 * -- nUNL size == 0
198 * -- no ToDisable
199 * -- no ToReEnable
200 *
201 * (14) next flag ledger
202 * -- nUNL size == 0
203 * -- no ToDisable
204 * -- no ToReEnable
205 */
206
207 testcase("Create UNLModify Tx and apply to ledgers");
208
211 // genesis ledger
214 Rules{env.app().config().features},
215 env.app().config().FEES.toFees(),
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
227 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
228 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
229
230 OpenView accum(&*l);
231 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
232 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
233 accum.apply(*l);
234 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
235 }
236
237 {
238 //(2) a flag ledger
239 // generate more ledgers
240 for (auto i = 0; i < 256 - 2; ++i)
241 {
243 }
244 BEAST_EXPECT(l->isFlagLedger());
245 l->updateNegativeUNL();
246
247 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
248 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
249 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
250
251 // can apply 1 and only 1 ToDisable Tx,
252 // cannot apply ToReEnable Tx, since negative UNL is empty
253 OpenView accum(&*l);
254 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
255 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, false));
256 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
257 accum.apply(*l);
258 auto good_size = negUnlSizeTest(l, 0, true, false);
259 BEAST_EXPECT(good_size);
260 if (good_size)
261 {
262 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
263 //++ first ToDisable Tx in ledger's TxSet
264 uint256 const txID = txDisable_0.getTransactionID();
265 BEAST_EXPECT(l->txExists(txID));
266 }
267 }
268
269 {
270 //(3) ledgers before the next flag ledger
271 for (auto i = 0; i < 256; ++i)
272 {
273 auto good_size = negUnlSizeTest(l, 0, true, false);
274 BEAST_EXPECT(good_size);
275 if (good_size)
276 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
278 }
279 BEAST_EXPECT(l->isFlagLedger());
280 l->updateNegativeUNL();
281
282 //(4) next flag ledger
283 // test if the ledger updated correctly
284 auto good_size = negUnlSizeTest(l, 1, false, false);
285 BEAST_EXPECT(good_size);
286 if (good_size)
287 {
288 BEAST_EXPECT(*(l->negativeUNL().begin()) == publicKeys[0]);
289 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
290 }
291
292 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
293 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
294 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
295 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
296 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
297
298 OpenView accum(&*l);
299 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
300 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, true));
301 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
302 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
303 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
304 accum.apply(*l);
305 good_size = negUnlSizeTest(l, 1, true, true);
306 BEAST_EXPECT(good_size);
307 if (good_size)
308 {
309 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
310 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
311 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
312 // test sfFirstLedgerSequence
313 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
314 }
315 }
316
317 {
318 //(5) ledgers before the next flag ledger
319 for (auto i = 0; i < 256; ++i)
320 {
321 auto good_size = negUnlSizeTest(l, 1, true, true);
322 BEAST_EXPECT(good_size);
323 if (good_size)
324 {
325 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
326 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
327 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
328 }
330 }
331 BEAST_EXPECT(l->isFlagLedger());
332 l->updateNegativeUNL();
333
334 //(6) next flag ledger
335 // test if the ledger updated correctly
336 auto good_size = negUnlSizeTest(l, 1, false, false);
337 BEAST_EXPECT(good_size);
338 if (good_size)
339 {
340 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
341 }
342
343 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
344
345 OpenView accum(&*l);
346 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
347 accum.apply(*l);
348 good_size = negUnlSizeTest(l, 1, true, false);
349 BEAST_EXPECT(good_size);
350 if (good_size)
351 {
352 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
353 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
354 nUnlLedgerSeq.emplace(publicKeys[1], l->seq());
355 nUnlLedgerSeq.erase(publicKeys[0]);
356 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
357 }
358 }
359
360 {
361 //(7) ledgers before the next flag ledger
362 for (auto i = 0; i < 256; ++i)
363 {
364 auto good_size = negUnlSizeTest(l, 1, true, false);
365 BEAST_EXPECT(good_size);
366 if (good_size)
367 {
368 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
369 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
370 }
372 }
373 BEAST_EXPECT(l->isFlagLedger());
374 l->updateNegativeUNL();
375
376 //(8) next flag ledger
377 // test if the ledger updated correctly
378 auto good_size = negUnlSizeTest(l, 2, false, false);
379 BEAST_EXPECT(good_size);
380 if (good_size)
381 {
382 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
383 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
384 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
385 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
386 }
387
388 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
389 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
390 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
391
392 OpenView accum(&*l);
393 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
394 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
395 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
396 accum.apply(*l);
397 good_size = negUnlSizeTest(l, 2, false, true);
398 BEAST_EXPECT(good_size);
399 if (good_size)
400 {
401 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
402 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
403 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
404 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
405 }
406 }
407
408 {
409 //(9) ledgers before the next flag ledger
410 for (auto i = 0; i < 256; ++i)
411 {
412 auto good_size = negUnlSizeTest(l, 2, false, true);
413 BEAST_EXPECT(good_size);
414 if (good_size)
415 {
416 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
417 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
418 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
419 }
421 }
422 BEAST_EXPECT(l->isFlagLedger());
423 l->updateNegativeUNL();
424
425 //(10) next flag ledger
426 // test if the ledger updated correctly
427 auto good_size = negUnlSizeTest(l, 1, false, false);
428 BEAST_EXPECT(good_size);
429 if (good_size)
430 {
431 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
432 nUnlLedgerSeq.erase(publicKeys[0]);
433 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
434 }
435
436 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
437
438 OpenView accum(&*l);
439 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, true));
440 accum.apply(*l);
441 good_size = negUnlSizeTest(l, 1, false, true);
442 BEAST_EXPECT(good_size);
443 if (good_size)
444 {
445 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
446 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
447 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
448 }
449 }
450
451 {
452 //(11) ledgers before the next flag ledger
453 for (auto i = 0; i < 256; ++i)
454 {
455 auto good_size = negUnlSizeTest(l, 1, false, true);
456 BEAST_EXPECT(good_size);
457 if (good_size)
458 {
459 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
460 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
461 }
463 }
464 BEAST_EXPECT(l->isFlagLedger());
465 l->updateNegativeUNL();
466
467 //(12) next flag ledger
468 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
469 }
470
471 {
472 //(13) ledgers before the next flag ledger
473 for (auto i = 0; i < 256; ++i)
474 {
475 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
477 }
478 BEAST_EXPECT(l->isFlagLedger());
479 l->updateNegativeUNL();
480
481 //(14) next flag ledger
482 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
483 }
484 }
485
486 void
487 run() override
488 {
490 }
491};
492
497{
505 {
506 std::uint32_t numNodes; // number of validators
507 std::uint32_t negUNLSize; // size of negative UNL in the last ledger
508 bool hasToDisable; // if has ToDisable in the last ledger
509 bool hasToReEnable; // if has ToReEnable in the last ledger
515 };
516
518 : env(suite, jtx::testable_amendments()), param(p), validations(env.app().getValidations())
519 {
520 createNodes();
521 if (!param.numLedgers)
522 param.numLedgers = 256 * (param.negUNLSize + 1);
524 }
525
526 void
528 {
529 assert(param.numNodes <= 256);
531 for (int i = 0; i < param.numNodes; ++i)
532 {
533 UNLKeySet.insert(UNLKeys[i]);
534 UNLNodeIDs.push_back(calcNodeID(UNLKeys[i]));
535 UNLNodeIDSet.insert(UNLNodeIDs.back());
536 }
537 }
538
543 bool
545 {
546 static uint256 fake_amendment; // So we have different genesis ledgers
549 Rules{env.app().config().features},
550 env.app().config().FEES.toFees(),
551 std::vector<uint256>{fake_amendment++},
552 env.app().getNodeFamily());
554
555 // When putting validators into the negative UNL, we start with
556 // validator 0, then validator 1 ...
557 int nidx = 0;
558 while (l->seq() <= param.numLedgers)
559 {
562
563 if (l->isFlagLedger())
564 {
565 l->updateNegativeUNL();
566 OpenView accum(&*l);
567 if (l->negativeUNL().size() < param.negUNLSize)
568 {
569 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
570 if (!applyAndTestResult(env, accum, tx, true))
571 break;
572 ++nidx;
573 }
574 else if (l->negativeUNL().size() == param.negUNLSize)
575 {
577 {
578 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
579 if (!applyAndTestResult(env, accum, tx, true))
580 break;
581 ++nidx;
582 }
584 {
585 auto tx = createTx(false, l->seq(), UNLKeys[0]);
586 if (!applyAndTestResult(env, accum, tx, true))
587 break;
588 }
589 }
590 accum.apply(*l);
591 }
592 l->updateSkipList();
593 }
595 }
596
605 {
606 static auto keyPair = randomKeyPair(KeyType::secp256k1);
608 env.app().getTimeKeeper().now(),
609 keyPair.first,
610 keyPair.second,
611 v,
612 [&](STValidation& v) {
613 v.setFieldH256(sfLedgerHash, ledger->header().hash);
614 v.setFieldU32(sfLedgerSequence, ledger->seq());
615 v.setFlag(vfFullValidation);
616 });
617 };
618
626 template <class NeedValidation>
627 void
628 walkHistoryAndAddValidations(NeedValidation&& needVal)
629 {
630 std::uint32_t curr = 0;
631 std::size_t const need = 256 + 1;
632 // only last 256 + 1 ledgers need validations
633 if (history.size() > need)
634 curr = history.size() - need;
635 for (; curr != history.size(); ++curr)
636 {
637 for (std::size_t i = 0; i < param.numNodes; ++i)
638 {
639 if (needVal(history[curr], i))
640 {
642 v.setTrusted();
644 }
645 }
646 }
647 }
648
651 {
652 return history.back();
653 }
654
664};
665
666auto defaultPreVote = [](NegativeUNLVote& vote) {};
678template <typename PreVote = decltype(defaultPreVote)>
679bool
681 NetworkHistory& history,
682 NodeID const& myId,
683 std::size_t expect,
684 PreVote const& pre = defaultPreVote)
685{
686 NegativeUNLVote vote(myId, history.env.journal);
687 pre(vote);
688 auto txSet =
690 vote.doVoting(history.lastLedger(), history.UNLKeySet, history.validations, txSet);
691 return countTx(txSet) == expect;
692}
693
698{
699 void
701 {
702 testcase("Create UNLModify Tx");
703 jtx::Env env(*this);
704
705 NodeID const myId(0xA0);
706 NegativeUNLVote vote(myId, env.journal);
707
708 // one add, one remove
712 LedgerIndex const seq(1234);
713 BEAST_EXPECT(countTx(txSet) == 0);
714 vote.addTx(seq, toDisableKey, NegativeUNLVote::ToDisable, txSet);
715 BEAST_EXPECT(countTx(txSet) == 1);
716 vote.addTx(seq, toReEnableKey, NegativeUNLVote::ToReEnable, txSet);
717 BEAST_EXPECT(countTx(txSet) == 2);
718 // content of a tx is implicitly tested after applied to a ledger
719 // in later test cases
720 }
721
722 void
724 {
725 testcase("Pick One Candidate");
726 jtx::Env const env(*this);
727
728 NodeID const myId(0xA0);
729 NegativeUNLVote const vote(myId, env.journal);
730
731 uint256 const pad_0(0);
732 uint256 const pad_f = ~pad_0;
733 NodeID const n_1(1);
734 NodeID const n_2(2);
735 NodeID const n_3(3);
736 std::vector<NodeID> candidates({n_1});
737 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
738 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_1);
739 candidates.emplace_back(2);
740 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
741 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_2);
742 candidates.emplace_back(3);
743 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
744 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_3);
745 }
746
747 void
749 {
750 testcase("Build Score Table");
751 /*
752 * 1. no skip list
753 * 2. short skip list
754 * 3. local node not enough history
755 * 4. a node double validated some seq
756 * 5. local node had enough validations but on a wrong chain
757 * 6. a good case, long enough history and perfect scores
758 */
759 {
760 // 1. no skip list
761 NetworkHistory history = {*this, {10, 0, false, false, 1}};
762 BEAST_EXPECT(history.goodHistory);
763 if (history.goodHistory)
764 {
765 NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal);
766 BEAST_EXPECT(!vote.buildScoreTable(
767 history.lastLedger(), history.UNLNodeIDSet, history.validations));
768 }
769 }
770
771 {
772 // 2. short skip list
773 NetworkHistory history = {*this, {10, 0, false, false, 256 / 2}};
774 BEAST_EXPECT(history.goodHistory);
775 if (history.goodHistory)
776 {
777 NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal);
778 BEAST_EXPECT(!vote.buildScoreTable(
779 history.lastLedger(), history.UNLNodeIDSet, history.validations));
780 }
781 }
782
783 {
784 // 3. local node not enough history
785 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
786 BEAST_EXPECT(history.goodHistory);
787 if (history.goodHistory)
788 {
789 NodeID myId = history.UNLNodeIDs[3];
791 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
792 // skip half my validations.
793 return history.UNLNodeIDs[idx] != myId || l->seq() % 2 != 0;
794 });
795 NegativeUNLVote vote(myId, history.env.journal);
796 BEAST_EXPECT(!vote.buildScoreTable(
797 history.lastLedger(), history.UNLNodeIDSet, history.validations));
798 }
799 }
800
801 {
802 // 4. a node double validated some seq
803 // 5. local node had enough validations but on a wrong chain
804 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
805 // We need two chains for these tests
806 bool const wrongChainSuccess = history.goodHistory;
807 BEAST_EXPECT(wrongChainSuccess);
808 NetworkHistory::LedgerHistory wrongChain = std::move(history.history);
809 // Create a new chain and use it as the one that majority of nodes
810 // follow
811 history.createLedgerHistory();
812 BEAST_EXPECT(history.goodHistory);
813
814 if (history.goodHistory && wrongChainSuccess)
815 {
816 NodeID myId = history.UNLNodeIDs[3];
817 NodeID const badNode = history.UNLNodeIDs[4];
819 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
820 // everyone but me
821 return !(history.UNLNodeIDs[idx] == myId);
822 });
823
824 // local node validate wrong chain
825 // a node double validates
826 for (auto& l : wrongChain)
827 {
828 RCLValidation const v1(history.createSTVal(l, myId));
829 history.validations.add(myId, v1);
830 RCLValidation const v2(history.createSTVal(l, badNode));
831 history.validations.add(badNode, v2);
832 }
833
834 NegativeUNLVote vote(myId, history.env.journal);
835
836 // local node still on wrong chain, can build a scoreTable,
837 // but all other nodes' scores are zero
838 auto scoreTable = vote.buildScoreTable(
839 wrongChain.back(), history.UNLNodeIDSet, history.validations);
840 BEAST_EXPECT(scoreTable);
841 if (scoreTable)
842 {
843 for (auto const& [n, score] : *scoreTable)
844 {
845 if (n == myId)
846 {
847 BEAST_EXPECT(score == 256);
848 }
849 else
850 {
851 BEAST_EXPECT(score == 0);
852 }
853 }
854 }
855
856 // if local node switched to right history, but cannot build
857 // scoreTable because not enough local validations
858 BEAST_EXPECT(!vote.buildScoreTable(
859 history.lastLedger(), history.UNLNodeIDSet, history.validations));
860 }
861 }
862
863 {
864 // 6. a good case
865 NetworkHistory history = {*this, {10, 0, false, false, 256 + 1}};
866 BEAST_EXPECT(history.goodHistory);
867 if (history.goodHistory)
868 {
870 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
871 return true;
872 });
873 NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal);
874 auto scoreTable = vote.buildScoreTable(
875 history.lastLedger(), history.UNLNodeIDSet, history.validations);
876 BEAST_EXPECT(scoreTable);
877 if (scoreTable)
878 {
879 for (auto const& [_, score] : *scoreTable)
880 {
881 (void)_;
882 BEAST_EXPECT(score == 256);
883 }
884 }
885 }
886 }
887 }
888
901 static bool
903 NegativeUNLVote& vote,
904 hash_set<NodeID> const& unl,
905 hash_set<NodeID> const& negUnl,
906 hash_map<NodeID, std::uint32_t> const& scoreTable,
907 std::size_t numDisable,
908 std::size_t numReEnable)
909 {
910 auto [disableCandidates, reEnableCandidates] =
911 vote.findAllCandidates(unl, negUnl, scoreTable);
912 bool const rightDisable = disableCandidates.size() == numDisable;
913 bool const rightReEnable = reEnableCandidates.size() == numReEnable;
914 return rightDisable && rightReEnable;
915 };
916
917 void
919 {
920 testcase("Find All Candidates");
921 /*
922 * -- unl size: 35
923 * -- negUnl size: 3
924 *
925 * 0. all good scores
926 * 1. all bad scores
927 * 2. all between watermarks
928 * 3. 2 good scorers in negUnl
929 * 4. 2 bad scorers not in negUnl
930 * 5. 2 in negUnl but not in unl, have a remove candidate from score
931 * table
932 * 6. 2 in negUnl but not in unl, no remove candidate from score table
933 * 7. 2 new validators have good scores, already in negUnl
934 * 8. 2 new validators have bad scores, not in negUnl
935 * 9. expired the new validators have bad scores, not in negUnl
936 */
937 NetworkHistory history = {*this, {35, 0, false, false, 0}};
938
939 hash_set<NodeID> negUnl_012;
940 for (std::uint32_t i = 0; i < 3; ++i)
941 negUnl_012.insert(history.UNLNodeIDs[i]);
942
943 // build a good scoreTable to use, or copy and modify
944 hash_map<NodeID, std::uint32_t> goodScoreTable;
945 for (auto const& n : history.UNLNodeIDs)
946 goodScoreTable[n] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
947
948 NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal);
949
950 {
951 // all good scores
952 BEAST_EXPECT(
953 checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, goodScoreTable, 0, 3));
954 }
955 {
956 // all bad scores
958 for (auto& n : history.UNLNodeIDs)
960 BEAST_EXPECT(
961 checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 35 - 3, 0));
962 }
963 {
964 // all between watermarks
966 for (auto& n : history.UNLNodeIDs)
968 BEAST_EXPECT(
969 checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 0));
970 }
971
972 {
973 // 2 good scorers in negUnl
974 auto scoreTable = goodScoreTable;
975 scoreTable[*negUnl_012.begin()] = NegativeUNLVote::negativeUNLLowWaterMark + 1;
976 BEAST_EXPECT(
977 checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 2));
978 }
979
980 {
981 // 2 bad scorers not in negUnl
982 auto scoreTable = goodScoreTable;
983 scoreTable[history.UNLNodeIDs[11]] = NegativeUNLVote::negativeUNLLowWaterMark - 1;
984 scoreTable[history.UNLNodeIDs[12]] = NegativeUNLVote::negativeUNLLowWaterMark - 1;
985 BEAST_EXPECT(
986 checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 2, 3));
987 }
988
989 {
990 // 2 in negUnl but not in unl, have a remove candidate from score
991 // table
992 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
993 UNL_temp.erase(history.UNLNodeIDs[0]);
994 UNL_temp.erase(history.UNLNodeIDs[1]);
995 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, goodScoreTable, 0, 3));
996 }
997
998 {
999 // 2 in negUnl but not in unl, no remove candidate from score table
1000 auto scoreTable = goodScoreTable;
1001 scoreTable.erase(history.UNLNodeIDs[0]);
1002 scoreTable.erase(history.UNLNodeIDs[1]);
1003 scoreTable[history.UNLNodeIDs[2]] = NegativeUNLVote::negativeUNLLowWaterMark + 1;
1004 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1005 UNL_temp.erase(history.UNLNodeIDs[0]);
1006 UNL_temp.erase(history.UNLNodeIDs[1]);
1007 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, scoreTable, 0, 2));
1008 }
1009
1010 {
1011 // 2 new validators
1012 NodeID const new_1(0xbead);
1013 NodeID const new_2(0xbeef);
1014 hash_set<NodeID> const nowTrusted = {new_1, new_2};
1015 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1016 UNL_temp.insert(new_1);
1017 UNL_temp.insert(new_2);
1018 vote.newValidators(256, nowTrusted);
1019 {
1020 // 2 new validators have good scores, already in negUnl
1021 auto scoreTable = goodScoreTable;
1022 scoreTable[new_1] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
1023 scoreTable[new_2] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
1024 hash_set<NodeID> negUnl_temp = negUnl_012;
1025 negUnl_temp.insert(new_1);
1026 negUnl_temp.insert(new_2);
1027 BEAST_EXPECT(
1028 checkCandidateSizes(vote, UNL_temp, negUnl_temp, scoreTable, 0, 3 + 2));
1029 }
1030 {
1031 // 2 new validators have bad scores, not in negUnl
1032 auto scoreTable = goodScoreTable;
1033 scoreTable[new_1] = 0;
1034 scoreTable[new_2] = 0;
1035 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, scoreTable, 0, 3));
1036 }
1037 {
1038 // expired the new validators have bad scores, not in negUnl
1040 auto scoreTable = goodScoreTable;
1041 scoreTable[new_1] = 0;
1042 scoreTable[new_2] = 0;
1043 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, scoreTable, 2, 3));
1044 }
1045 }
1046 }
1047
1048 void
1050 {
1051 testcase("Find All Candidates Combination");
1052 /*
1053 * == combination 1:
1054 * -- unl size: 34, 35, 80
1055 * -- nUnl size: 0, 50%, all
1056 * -- score pattern: all 0, all negativeUNLLowWaterMark & +1 & -1, all
1057 * negativeUNLHighWaterMark & +1 & -1, all 100%
1058 *
1059 * == combination 2:
1060 * -- unl size: 34, 35, 80
1061 * -- negativeUNL size: 0, all
1062 * -- nUnl size: one on, one off, one on, one off,
1063 * -- score pattern: 2*(negativeUNLLowWaterMark, +1, -1) &
1064 * 2*(negativeUNLHighWaterMark, +1, -1) & rest
1065 * negativeUNLMinLocalValsToVote
1066 */
1067
1068 jtx::Env const env(*this);
1069
1070 NodeID const myId(0xA0);
1071 NegativeUNLVote vote(myId, env.journal);
1072
1073 std::array<std::uint32_t, 3> const unlSizes = {34, 35, 80};
1074 std::array<std::uint32_t, 3> const nUnlPercent = {0, 50, 100};
1076 0,
1084
1085 //== combination 1:
1086 {
1087 auto fillScoreTable = [&](std::uint32_t unl_size,
1088 std::uint32_t nUnl_size,
1089 std::uint32_t score,
1090 hash_set<NodeID>& unl,
1091 hash_set<NodeID>& negUnl,
1092 hash_map<NodeID, std::uint32_t>& scoreTable) {
1093 std::vector<NodeID> nodeIDs;
1094 std::vector<PublicKey> const keys = createPublicKeys(unl_size);
1095 for (auto const& k : keys)
1096 {
1097 nodeIDs.emplace_back(calcNodeID(k));
1098 unl.emplace(nodeIDs.back());
1099 scoreTable[nodeIDs.back()] = score;
1100 }
1101 for (std::uint32_t i = 0; i < nUnl_size; ++i)
1102 negUnl.insert(nodeIDs[i]);
1103 };
1104
1105 for (auto us : unlSizes)
1106 {
1107 for (auto np : nUnlPercent)
1108 {
1109 for (auto score : scores)
1110 {
1111 hash_set<NodeID> unl;
1112 hash_set<NodeID> negUnl;
1114 fillScoreTable(us, us * np / 100, score, unl, negUnl, scoreTable);
1115 BEAST_EXPECT(unl.size() == us);
1116 BEAST_EXPECT(negUnl.size() == us * np / 100);
1117 BEAST_EXPECT(scoreTable.size() == us);
1118
1119 std::size_t toDisable_expect = 0;
1120 std::size_t toReEnable_expect = 0;
1121 if (np == 0)
1122 {
1124 {
1125 toDisable_expect = us;
1126 }
1127 }
1128 else if (np == 50)
1129 {
1131 {
1132 toReEnable_expect = us * np / 100;
1133 }
1134 }
1135 else
1136 {
1138 {
1139 toReEnable_expect = us;
1140 }
1141 }
1142 BEAST_EXPECT(checkCandidateSizes(
1143 vote, unl, negUnl, scoreTable, toDisable_expect, toReEnable_expect));
1144 }
1145 }
1146 }
1147
1148 //== combination 2:
1149 {
1150 auto fillScoreTable = [&](std::uint32_t unl_size,
1151 std::uint32_t nUnl_percent,
1152 hash_set<NodeID>& unl,
1153 hash_set<NodeID>& negUnl,
1154 hash_map<NodeID, std::uint32_t>& scoreTable) {
1155 std::vector<NodeID> nodeIDs;
1156 std::vector<PublicKey> const keys = createPublicKeys(unl_size);
1157 for (auto const& k : keys)
1158 {
1159 nodeIDs.emplace_back(calcNodeID(k));
1160 unl.emplace(nodeIDs.back());
1161 }
1162
1163 std::uint32_t nIdx = 0;
1164 for (auto score : scores)
1165 {
1166 scoreTable[nodeIDs[nIdx++]] = score;
1167 scoreTable[nodeIDs[nIdx++]] = score;
1168 }
1169 for (; nIdx < unl_size;)
1170 {
1171 scoreTable[nodeIDs[nIdx++]] = scores.back();
1172 }
1173
1174 if (nUnl_percent == 100)
1175 {
1176 negUnl = unl;
1177 }
1178 else if (nUnl_percent == 50)
1179 {
1180 for (std::uint32_t i = 1; i < unl_size; i += 2)
1181 negUnl.insert(nodeIDs[i]);
1182 }
1183 };
1184
1185 for (auto us : unlSizes)
1186 {
1187 for (auto np : nUnlPercent)
1188 {
1189 hash_set<NodeID> unl;
1190 hash_set<NodeID> negUnl;
1192
1193 fillScoreTable(us, np, unl, negUnl, scoreTable);
1194 BEAST_EXPECT(unl.size() == us);
1195 BEAST_EXPECT(negUnl.size() == us * np / 100);
1196 BEAST_EXPECT(scoreTable.size() == us);
1197
1198 std::size_t toDisable_expect = 0;
1199 std::size_t toReEnable_expect = 0;
1200 if (np == 0)
1201 {
1202 toDisable_expect = 4;
1203 }
1204 else if (np == 50)
1205 {
1206 toReEnable_expect = negUnl.size() - 6;
1207 }
1208 else
1209 {
1210 toReEnable_expect = negUnl.size() - 12;
1211 }
1212 BEAST_EXPECT(checkCandidateSizes(
1213 vote, unl, negUnl, scoreTable, toDisable_expect, toReEnable_expect));
1214 }
1215 }
1216 }
1217 }
1218 }
1219
1220 void
1222 {
1223 testcase("New Validators");
1224 jtx::Env const env(*this);
1225
1226 NodeID const myId(0xA0);
1227 NegativeUNLVote vote(myId, env.journal);
1228
1229 // test cases:
1230 // newValidators_ of the NegativeUNLVote empty, add one
1231 // add a new one and one already added
1232 // add a new one and some already added
1233 // purge and see some are expired
1234
1235 NodeID const n1(0xA1);
1236 NodeID const n2(0xA2);
1237 NodeID const n3(0xA3);
1238
1239 vote.newValidators(2, {n1});
1240 BEAST_EXPECT(vote.newValidators_.size() == 1);
1241 if (vote.newValidators_.size() == 1)
1242 {
1243 BEAST_EXPECT(vote.newValidators_.begin()->first == n1);
1244 BEAST_EXPECT(vote.newValidators_.begin()->second == 2);
1245 }
1246
1247 vote.newValidators(3, {n1, n2});
1248 BEAST_EXPECT(vote.newValidators_.size() == 2);
1249 if (vote.newValidators_.size() == 2)
1250 {
1251 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1252 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1253 }
1254
1256 BEAST_EXPECT(vote.newValidators_.size() == 3);
1257 if (vote.newValidators_.size() == 3)
1258 {
1259 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1260 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1262 }
1263
1265 BEAST_EXPECT(vote.newValidators_.size() == 3);
1267 BEAST_EXPECT(vote.newValidators_.size() == 2);
1269 BEAST_EXPECT(vote.newValidators_.size() == 1);
1270 BEAST_EXPECT(vote.newValidators_.begin()->first == n3);
1271 BEAST_EXPECT(
1273 }
1274
1275 void
1285};
1286
1293{
1294 void
1296 {
1297 testcase("Build Score Table Combination");
1298 /*
1299 * local node good history, correct scores:
1300 * == combination:
1301 * -- unl size: 10, 34, 35, 50
1302 * -- score pattern: all 0, all 50%, all 100%, two 0% two 50% rest 100%
1303 */
1304 std::array<std::uint32_t, 4> const unlSizes = {10, 34, 35, 50};
1305 std::array<std::array<std::uint32_t, 3>, 4> scorePattern = {
1306 {{{0, 0, 0}}, {{50, 50, 50}}, {{100, 100, 100}}, {{0, 50, 100}}}};
1307
1308 for (auto unlSize : unlSizes)
1309 {
1310 for (std::uint32_t sp = 0; sp < 4; ++sp)
1311 {
1312 NetworkHistory history = {*this, {unlSize, 0, false, false, 256 + 2}};
1313 BEAST_EXPECT(history.goodHistory);
1314 if (history.goodHistory)
1315 {
1316 NodeID myId = history.UNLNodeIDs[3];
1318 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1319 std::size_t k = 0;
1320 if (idx < 2)
1321 {
1322 k = 0;
1323 }
1324 else if (idx < 4)
1325 {
1326 k = 1;
1327 }
1328 else
1329 {
1330 k = 2;
1331 }
1332
1333 bool const add_50 = scorePattern[sp][k] == 50 && l->seq() % 2 == 0;
1334 bool const add_100 = scorePattern[sp][k] == 100;
1335 bool const add_me = history.UNLNodeIDs[idx] == myId;
1336 return add_50 || add_100 || add_me;
1337 });
1338
1339 NegativeUNLVote vote(myId, history.env.journal);
1340 auto scoreTable = vote.buildScoreTable(
1341 history.lastLedger(), history.UNLNodeIDSet, history.validations);
1342 BEAST_EXPECT(scoreTable);
1343 if (scoreTable)
1344 {
1345 std::uint32_t i = 0; // looping unl
1346 auto checkScores = [&](std::uint32_t score, std::uint32_t k) -> bool {
1347 if (history.UNLNodeIDs[i] == myId)
1348 return score == 256;
1349 if (scorePattern[sp][k] == 0)
1350 return score == 0;
1351 if (scorePattern[sp][k] == 50)
1352 return score == 256 / 2;
1353 if (scorePattern[sp][k] == 100)
1354 {
1355 return score == 256;
1356 }
1357
1358 return false;
1359 };
1360 for (; i < 2; ++i)
1361 {
1362 BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 0));
1363 }
1364 for (; i < 4; ++i)
1365 {
1366 BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 1));
1367 }
1368 for (; i < unlSize; ++i)
1369 {
1370 BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 2));
1371 }
1372 }
1373 }
1374 }
1375 }
1376 }
1377
1378 void
1379 run() override
1380 {
1382 }
1383};
1384
1385/*
1386 * Test the doVoting function of NegativeUNLVote.
1387 * The test cases are split to 5 classes for parallel execution.
1388 *
1389 * Voting tests: (use hasToDisable and hasToReEnable in some of the cases)
1390 *
1391 * == all good score, nUnl empty
1392 * -- txSet.size = 0
1393 * == all good score, nUnl not empty (use hasToDisable)
1394 * -- txSet.size = 1
1395 *
1396 * == 2 nodes offline, nUnl empty (use hasToReEnable)
1397 * -- txSet.size = 1
1398 * == 2 nodes offline, in nUnl
1399 * -- txSet.size = 0
1400 *
1401 * == 2 nodes offline, not in nUnl, but maxListed
1402 * -- txSet.size = 0
1403 *
1404 * == 2 nodes offline including me, not in nUnl
1405 * -- txSet.size = 0
1406 * == 2 nodes offline, not in negativeUNL, but I'm not a validator
1407 * -- txSet.size = 0
1408 * == 2 in nUnl, but not in unl, no other remove candidates
1409 * -- txSet.size = 1
1410 *
1411 * == 2 new validators have bad scores
1412 * -- txSet.size = 0
1413 * == 2 expired new validators have bad scores
1414 * -- txSet.size = 1
1415 */
1416
1418{
1419 void
1421 {
1422 testcase("Do Voting");
1423
1424 {
1425 //== all good score, negativeUNL empty
1426 //-- txSet.size = 0
1427 NetworkHistory history = {*this, {51, 0, false, false, {}}};
1428 BEAST_EXPECT(history.goodHistory);
1429 if (history.goodHistory)
1430 {
1432 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1433 return true;
1434 });
1435 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1436 }
1437 }
1438
1439 {
1440 // all good score, negativeUNL not empty (use hasToDisable)
1441 //-- txSet.size = 1
1442 NetworkHistory history = {*this, {37, 0, true, false, {}}};
1443 BEAST_EXPECT(history.goodHistory);
1444 if (history.goodHistory)
1445 {
1447 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1448 return true;
1449 });
1450 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 1));
1451 }
1452 }
1453 }
1454
1455 void
1456 run() override
1457 {
1458 testDoVoting();
1459 }
1460};
1461
1463{
1464 void
1466 {
1467 testcase("Do Voting");
1468
1469 {
1470 //== 2 nodes offline, negativeUNL empty (use hasToReEnable)
1471 //-- txSet.size = 1
1472 NetworkHistory history = {*this, {29, 1, false, true, {}}};
1473 BEAST_EXPECT(history.goodHistory);
1474 if (history.goodHistory)
1475 {
1477 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1478 // skip node 0 and node 1
1479 return idx > 1;
1480 });
1481 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 1));
1482 }
1483 }
1484
1485 {
1486 // 2 nodes offline, in negativeUNL
1487 //-- txSet.size = 0
1488 NetworkHistory history = {*this, {30, 1, true, false, {}}};
1489 BEAST_EXPECT(history.goodHistory);
1490 if (history.goodHistory)
1491 {
1492 NodeID n1 = calcNodeID(*history.lastLedger()->negativeUNL().begin());
1493 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1494 NodeID n2 = calcNodeID(*history.lastLedger()->validatorToDisable());
1496 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1497 // skip node 0 and node 1
1498 return history.UNLNodeIDs[idx] != n1 && history.UNLNodeIDs[idx] != n2;
1499 });
1500 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1501 }
1502 }
1503 }
1504
1505 void
1506 run() override
1507 {
1508 testDoVoting();
1509 }
1510};
1511
1513{
1514 void
1516 {
1517 testcase("Do Voting");
1518
1519 {
1520 // 2 nodes offline, not in negativeUNL, but maxListed
1521 //-- txSet.size = 0
1522 NetworkHistory history = {*this, {32, 8, true, true, {}}};
1523 BEAST_EXPECT(history.goodHistory);
1524 if (history.goodHistory)
1525 {
1527 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1528 // skip node 0 ~ 10
1529 return idx > 10;
1530 });
1531 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1532 }
1533 }
1534 }
1535
1536 void
1537 run() override
1538 {
1539 testDoVoting();
1540 }
1541};
1542
1544{
1545 void
1547 {
1548 testcase("Do Voting");
1549
1550 {
1551 //== 2 nodes offline including me, not in negativeUNL
1552 //-- txSet.size = 0
1553 NetworkHistory history = {*this, {35, 0, false, false, {}}};
1554 BEAST_EXPECT(history.goodHistory);
1555 if (history.goodHistory)
1556 {
1558 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1559 return idx > 1;
1560 });
1561 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1562 }
1563 }
1564
1565 {
1566 // 2 nodes offline, not in negativeUNL, but I'm not a validator
1567 //-- txSet.size = 0
1568 NetworkHistory history = {*this, {40, 0, false, false, {}}};
1569 BEAST_EXPECT(history.goodHistory);
1570 if (history.goodHistory)
1571 {
1573 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1574 return idx > 1;
1575 });
1576 BEAST_EXPECT(voteAndCheck(history, NodeID(0xdeadbeef), 0));
1577 }
1578 }
1579
1580 {
1581 //== 2 in negativeUNL, but not in unl, no other remove candidates
1582 //-- txSet.size = 1
1583 NetworkHistory history = {*this, {25, 2, false, false, {}}};
1584 BEAST_EXPECT(history.goodHistory);
1585 if (history.goodHistory)
1586 {
1588 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1589 return idx > 1;
1590 });
1591 BEAST_EXPECT(
1592 voteAndCheck(history, history.UNLNodeIDs.back(), 1, [&](NegativeUNLVote& vote) {
1593 history.UNLKeySet.erase(history.UNLKeys[0]);
1594 history.UNLKeySet.erase(history.UNLKeys[1]);
1595 }));
1596 }
1597 }
1598 }
1599
1600 void
1601 run() override
1602 {
1603 testDoVoting();
1604 }
1605};
1606
1608{
1609 void
1611 {
1612 testcase("Do Voting");
1613
1614 {
1615 //== 2 new validators have bad scores
1616 //-- txSet.size = 0
1617 NetworkHistory history = {*this, {15, 0, false, false, {}}};
1618 BEAST_EXPECT(history.goodHistory);
1619 if (history.goodHistory)
1620 {
1622 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1623 return true;
1624 });
1625 BEAST_EXPECT(
1626 voteAndCheck(history, history.UNLNodeIDs[0], 0, [&](NegativeUNLVote& vote) {
1627 auto extra_key_1 = randomKeyPair(KeyType::ed25519).first;
1628 auto extra_key_2 = randomKeyPair(KeyType::ed25519).first;
1629 history.UNLKeySet.insert(extra_key_1);
1630 history.UNLKeySet.insert(extra_key_2);
1631 hash_set<NodeID> nowTrusted;
1632 nowTrusted.insert(calcNodeID(extra_key_1));
1633 nowTrusted.insert(calcNodeID(extra_key_2));
1634 vote.newValidators(history.lastLedger()->seq(), nowTrusted);
1635 }));
1636 }
1637 }
1638
1639 {
1640 //== 2 expired new validators have bad scores
1641 //-- txSet.size = 1
1642 NetworkHistory history = {
1643 *this, {21, 0, false, false, NegativeUNLVote::newValidatorDisableSkip * 2}};
1644 BEAST_EXPECT(history.goodHistory);
1645 if (history.goodHistory)
1646 {
1648 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1649 return true;
1650 });
1651 BEAST_EXPECT(
1652 voteAndCheck(history, history.UNLNodeIDs[0], 1, [&](NegativeUNLVote& vote) {
1653 auto extra_key_1 = randomKeyPair(KeyType::ed25519).first;
1654 auto extra_key_2 = randomKeyPair(KeyType::ed25519).first;
1655 history.UNLKeySet.insert(extra_key_1);
1656 history.UNLKeySet.insert(extra_key_2);
1657 hash_set<NodeID> nowTrusted;
1658 nowTrusted.insert(calcNodeID(extra_key_1));
1659 nowTrusted.insert(calcNodeID(extra_key_2));
1660 vote.newValidators(256, nowTrusted);
1661 }));
1662 }
1663 }
1664 }
1665
1666 void
1667 run() override
1668 {
1669 testDoVoting();
1670 }
1671};
1672
1674{
1675 void
1677 {
1678 testcase("Filter Validations");
1679 jtx::Env env(*this);
1680 auto l = std::make_shared<Ledger>(
1682 Rules{env.app().config().features},
1683 env.app().config().FEES.toFees(),
1685 env.app().getNodeFamily());
1686
1687 auto createSTVal = [&](std::pair<PublicKey, SecretKey> const& keys) {
1689 env.app().getTimeKeeper().now(),
1690 keys.first,
1691 keys.second,
1692 calcNodeID(keys.first),
1693 [&](STValidation& v) {
1694 v.setFieldH256(sfLedgerHash, l->header().hash);
1695 v.setFieldU32(sfLedgerSequence, l->seq());
1696 v.setFlag(vfFullValidation);
1697 });
1698 };
1699
1700 // create keys and validations
1701 std::uint32_t const numNodes = 10;
1702 std::uint32_t const negUnlSize = 3;
1704 hash_set<NodeID> activeValidators;
1705 hash_set<PublicKey> nUnlKeys;
1707 for (int i = 0; i < numNodes; ++i)
1708 {
1709 auto keyPair = randomKeyPair(KeyType::secp256k1);
1710 vals.emplace_back(createSTVal(keyPair));
1711 cfgKeys.push_back(toBase58(TokenType::NodePublic, keyPair.first));
1712 activeValidators.emplace(calcNodeID(keyPair.first));
1713 if (i < negUnlSize)
1714 {
1715 nUnlKeys.insert(keyPair.first);
1716 }
1717 }
1718
1719 // setup the ValidatorList
1720 auto& validators = env.app().getValidators();
1721 auto& local = *nUnlKeys.begin();
1722 std::vector<std::string> const cfgPublishers;
1723 validators.load(local, cfgKeys, cfgPublishers);
1724 validators.updateTrusted(
1725 activeValidators,
1726 env.timeKeeper().now(),
1727 env.app().getOPs(),
1728 env.app().getOverlay(),
1729 env.app().getHashRouter());
1730 BEAST_EXPECT(validators.getTrustedMasterKeys().size() == numNodes);
1731 validators.setNegativeUNL(nUnlKeys);
1732 BEAST_EXPECT(validators.getNegativeUNL().size() == negUnlSize);
1733
1734 // test the filter
1735 BEAST_EXPECT(vals.size() == numNodes);
1736 vals = validators.negativeUNLFilter(std::move(vals));
1737 BEAST_EXPECT(vals.size() == numNodes - negUnlSize);
1738 }
1739
1740 void
1741 run() override
1742 {
1744 }
1745};
1746
1747BEAST_DEFINE_TESTSUITE(NegativeUNL, consensus, xrpl);
1748
1749BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, xrpl);
1750BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, xrpl);
1751BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteGoodScore, consensus, xrpl, 1);
1752BEAST_DEFINE_TESTSUITE(NegativeUNLVoteOffline, consensus, xrpl);
1753BEAST_DEFINE_TESTSUITE(NegativeUNLVoteMaxListed, consensus, xrpl);
1754BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteRetiredValidator, consensus, xrpl, 1);
1755BEAST_DEFINE_TESTSUITE(NegativeUNLVoteNewValidator, consensus, xrpl);
1756BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, xrpl);
1757
1761bool
1764 size_t size,
1765 bool hasToDisable,
1766 bool hasToReEnable)
1767{
1768 bool const sameSize = l->negativeUNL().size() == size;
1769 bool const sameToDisable = (l->validatorToDisable() != std::nullopt) == hasToDisable;
1770 bool const sameToReEnable = (l->validatorToReEnable() != std::nullopt) == hasToReEnable;
1771
1772 return sameSize && sameToDisable && sameToReEnable;
1773}
1774
1775bool
1776applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
1777{
1778 auto const res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
1779 if (pass)
1780 {
1781 return isTesSuccess(res.ter);
1782 }
1783
1784 return res.ter == tefFAILURE || res.ter == temDISABLED;
1785}
1786
1787bool
1791{
1792 auto sle = l->read(keylet::negativeUNL());
1793 if (!sle)
1794 return false;
1795 if (!sle->isFieldPresent(sfDisabledValidators))
1796 return false;
1797
1798 auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
1799 if (nUnlData.size() != nUnlLedgerSeq.size())
1800 return false;
1801
1802 for (auto const& n : nUnlData)
1803 {
1804 if (!n.isFieldPresent(sfFirstLedgerSequence) || !n.isFieldPresent(sfPublicKey))
1805 return false;
1806
1807 auto seq = n.getFieldU32(sfFirstLedgerSequence);
1808 auto d = n.getFieldVL(sfPublicKey);
1809 auto s = makeSlice(d);
1810 if (!publicKeyType(s))
1811 return false;
1812 PublicKey const pk(s);
1813 auto it = nUnlLedgerSeq.find(pk);
1814 if (it == nUnlLedgerSeq.end())
1815 return false;
1816 if (it->second != seq)
1817 return false;
1818 nUnlLedgerSeq.erase(it);
1819 }
1820 return nUnlLedgerSeq.empty();
1821}
1822
1825{
1826 std::size_t count = 0;
1827 for (auto i = txSet->begin(); i != txSet->end(); ++i)
1828 {
1829 ++count;
1830 }
1831 return count;
1832};
1833
1836{
1838 std::size_t const ss = 33;
1840 data[0] = 0xED;
1841 for (int i = 0; i < n; ++i)
1842 {
1843 data[1]++;
1844 Slice const s(data.data(), ss);
1845 keys.emplace_back(s);
1846 }
1847 return keys;
1848}
1849
1850STTx
1851createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
1852{
1853 auto fill = [&](auto& obj) {
1854 obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
1855 obj.setFieldU32(sfLedgerSequence, seq);
1856 obj.setFieldVL(sfUNLModifyValidator, txKey);
1857 };
1858 return STTx(ttUNL_MODIFY, fill);
1859}
1860
1861} // namespace test
1862} // namespace xrpl
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
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.
Candidates 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 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_
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:45
void apply(TxsRawView &to) const
Apply changes.
Definition OpenView.cpp:109
A public key.
Definition PublicKey.h:42
Wrapper over STValidation for generic Validation code.
Rules controlling protocol behavior.
Definition Rules.h:18
virtual NetworkOPs & getOPs()=0
virtual ValidatorList & getValidators()=0
virtual HashRouter & getHashRouter()=0
virtual Family & getNodeFamily()=0
virtual Overlay & getOverlay()=0
virtual TimeKeeper & getTimeKeeper()=0
An immutable linear range of bytes.
Definition Slice.h:26
time_point now() const override
Returns the current time, using the server's clock.
Definition TimeKeeper.h:44
time_point closeTime() const
Returns the predicted close time, in network time.
Definition TimeKeeper.h:56
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.
static 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:122
Application & app()
Definition Env.h:259
ManualTimeKeeper & timeKeeper()
Definition Env.h:271
beast::Journal const journal
Definition Env.h:163
T emplace_back(T... args)
T emplace(T... args)
T empty(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:207
auto const data
General field definitions, or fields used in multiple transaction namespaces.
FeatureBitset testable_amendments()
Definition Env.h:78
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:5
base_uint< 160, detail::NodeIDTag > NodeID
NodeID is a 160-bit hash representing one node.
Definition UintTypes.h:39
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(ServiceRegistry &registry, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:124
@ tefFAILURE
Definition TER.h:146
create_genesis_t const create_genesis
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
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:11
@ temDISABLED
Definition TER.h:94
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
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:215
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:14