rippled
Loading...
Searching...
No Matches
NegativeUNL_test.cpp
1#include <test/jtx.h>
2
3#include <xrpld/app/consensus/RCLValidations.h>
4#include <xrpld/app/ledger/Ledger.h>
5#include <xrpld/app/misc/NegativeUNLVote.h>
6#include <xrpld/app/misc/ValidatorList.h>
7#include <xrpld/app/tx/apply.h>
8
9#include <xrpl/beast/unit_test.h>
10#include <xrpl/ledger/View.h>
11
12namespace xrpl {
13namespace test {
14
15/*
16 * This file implements the following negative UNL related tests:
17 * -- test filling and applying ttUNL_MODIFY Tx and ledger update
18 * -- test the NegativeUNLVote class. The test cases are split to multiple
19 * test classes to allow parallel execution.
20 * -- test the negativeUNLFilter function
21 *
22 * Other negative UNL related tests such as ValidatorList and RPC related ones
23 * are put in their existing unit test files.
24 */
25
36bool
37negUnlSizeTest(std::shared_ptr<Ledger const> const& l, size_t size, bool hasToDisable, bool hasToReEnable);
38
48bool
49applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
50
60bool
61VerifyPubKeyAndSeq(std::shared_ptr<Ledger const> const& l, hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq);
62
71
80
89STTx
90createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
91
93{
101 void
103 {
104 /*
105 * test cases:
106 *
107 * (1) the ledger after genesis
108 * -- cannot apply Disable Tx
109 * -- cannot apply ReEnable Tx
110 * -- nUNL empty
111 * -- no ToDisable
112 * -- no ToReEnable
113 *
114 * (2) a flag ledger
115 * -- apply an Disable Tx
116 * -- cannot apply the second Disable Tx
117 * -- cannot apply a ReEnable Tx
118 * -- nUNL empty
119 * -- has ToDisable with right nodeId
120 * -- no ToReEnable
121 * ++ extra test: first Disable Tx in ledger TxSet
122 *
123 * (3) ledgers before the next flag ledger
124 * -- nUNL empty
125 * -- has ToDisable with right nodeId
126 * -- no ToReEnable
127 *
128 * (4) next flag ledger
129 * -- nUNL size == 1, with right nodeId
130 * -- no ToDisable
131 * -- no ToReEnable
132 * -- cannot apply an Disable Tx with nodeId already in nUNL
133 * -- apply an Disable Tx with different nodeId
134 * -- cannot apply a ReEnable Tx with the same NodeId as Add
135 * -- cannot apply a ReEnable Tx with a NodeId not in nUNL
136 * -- apply a ReEnable Tx with a nodeId already in nUNL
137 * -- has ToDisable with right nodeId
138 * -- has ToReEnable with right nodeId
139 * -- nUNL size still 1, right nodeId
140 *
141 * (5) ledgers before the next flag ledger
142 * -- nUNL size == 1, right nodeId
143 * -- has ToDisable with right nodeId
144 * -- has ToReEnable with right nodeId
145 *
146 * (6) next flag ledger
147 * -- nUNL size == 1, different nodeId
148 * -- no ToDisable
149 * -- no ToReEnable
150 * -- apply an Disable Tx with different nodeId
151 * -- nUNL size still 1, right nodeId
152 * -- has ToDisable with right nodeId
153 * -- no ToReEnable
154 *
155 * (7) ledgers before the next flag ledger
156 * -- nUNL size still 1, right nodeId
157 * -- has ToDisable with right nodeId
158 * -- no ToReEnable
159 *
160 * (8) next flag ledger
161 * -- nUNL size == 2
162 * -- apply a ReEnable Tx
163 * -- cannot apply second ReEnable Tx, even with right nodeId
164 * -- cannot apply an Disable Tx with the same NodeId as Remove
165 * -- nUNL size == 2
166 * -- no ToDisable
167 * -- has ToReEnable with right nodeId
168 *
169 * (9) ledgers before the next flag ledger
170 * -- nUNL size == 2
171 * -- no ToDisable
172 * -- has ToReEnable with right nodeId
173 *
174 * (10) next flag ledger
175 * -- nUNL size == 1
176 * -- apply a ReEnable Tx
177 * -- nUNL size == 1
178 * -- no ToDisable
179 * -- has ToReEnable with right nodeId
180 *
181 * (11) ledgers before the next flag ledger
182 * -- nUNL size == 1
183 * -- no ToDisable
184 * -- has ToReEnable with right nodeId
185 *
186 * (12) next flag ledger
187 * -- nUNL size == 0
188 * -- no ToDisable
189 * -- no ToReEnable
190 *
191 * (13) ledgers before the next flag ledger
192 * -- nUNL size == 0
193 * -- no ToDisable
194 * -- no ToReEnable
195 *
196 * (14) next flag ledger
197 * -- nUNL size == 0
198 * -- no ToDisable
199 * -- no ToReEnable
200 */
201
202 testcase("Create UNLModify Tx and apply to ledgers");
203
206 // genesis ledger
209
210 // Record the public keys and ledger sequences of expected negative UNL
211 // validators when we build the ledger history
213
214 {
215 //(1) the ledger after genesis, not a flag ledger
217
218 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
219 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
220
221 OpenView accum(&*l);
222 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
223 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
224 accum.apply(*l);
225 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
226 }
227
228 {
229 //(2) a flag ledger
230 // generate more ledgers
231 for (auto i = 0; i < 256 - 2; ++i)
232 {
234 }
235 BEAST_EXPECT(l->isFlagLedger());
236 l->updateNegativeUNL();
237
238 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
239 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
240 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
241
242 // can apply 1 and only 1 ToDisable Tx,
243 // cannot apply ToReEnable Tx, since negative UNL is empty
244 OpenView accum(&*l);
245 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
246 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, false));
247 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
248 accum.apply(*l);
249 auto good_size = negUnlSizeTest(l, 0, true, false);
250 BEAST_EXPECT(good_size);
251 if (good_size)
252 {
253 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
254 //++ first ToDisable Tx in ledger's TxSet
255 uint256 txID = txDisable_0.getTransactionID();
256 BEAST_EXPECT(l->txExists(txID));
257 }
258 }
259
260 {
261 //(3) ledgers before the next flag ledger
262 for (auto i = 0; i < 256; ++i)
263 {
264 auto good_size = negUnlSizeTest(l, 0, true, false);
265 BEAST_EXPECT(good_size);
266 if (good_size)
267 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
269 }
270 BEAST_EXPECT(l->isFlagLedger());
271 l->updateNegativeUNL();
272
273 //(4) next flag ledger
274 // test if the ledger updated correctly
275 auto good_size = negUnlSizeTest(l, 1, false, false);
276 BEAST_EXPECT(good_size);
277 if (good_size)
278 {
279 BEAST_EXPECT(*(l->negativeUNL().begin()) == publicKeys[0]);
280 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
281 }
282
283 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
284 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
285 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
286 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
287 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
288
289 OpenView accum(&*l);
290 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
291 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, true));
292 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
293 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
294 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
295 accum.apply(*l);
296 good_size = negUnlSizeTest(l, 1, true, true);
297 BEAST_EXPECT(good_size);
298 if (good_size)
299 {
300 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
301 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
302 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
303 // test sfFirstLedgerSequence
304 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
305 }
306 }
307
308 {
309 //(5) ledgers before the next flag ledger
310 for (auto i = 0; i < 256; ++i)
311 {
312 auto good_size = negUnlSizeTest(l, 1, true, true);
313 BEAST_EXPECT(good_size);
314 if (good_size)
315 {
316 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
317 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
318 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
319 }
321 }
322 BEAST_EXPECT(l->isFlagLedger());
323 l->updateNegativeUNL();
324
325 //(6) next flag ledger
326 // test if the ledger updated correctly
327 auto good_size = negUnlSizeTest(l, 1, false, false);
328 BEAST_EXPECT(good_size);
329 if (good_size)
330 {
331 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
332 }
333
334 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
335
336 OpenView accum(&*l);
337 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
338 accum.apply(*l);
339 good_size = negUnlSizeTest(l, 1, true, false);
340 BEAST_EXPECT(good_size);
341 if (good_size)
342 {
343 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
344 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
345 nUnlLedgerSeq.emplace(publicKeys[1], l->seq());
346 nUnlLedgerSeq.erase(publicKeys[0]);
347 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
348 }
349 }
350
351 {
352 //(7) ledgers before the next flag ledger
353 for (auto i = 0; i < 256; ++i)
354 {
355 auto good_size = negUnlSizeTest(l, 1, true, false);
356 BEAST_EXPECT(good_size);
357 if (good_size)
358 {
359 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
360 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
361 }
363 }
364 BEAST_EXPECT(l->isFlagLedger());
365 l->updateNegativeUNL();
366
367 //(8) next flag ledger
368 // test if the ledger updated correctly
369 auto good_size = negUnlSizeTest(l, 2, false, false);
370 BEAST_EXPECT(good_size);
371 if (good_size)
372 {
373 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
374 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
375 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
376 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
377 }
378
379 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
380 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
381 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
382
383 OpenView accum(&*l);
384 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
385 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
386 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
387 accum.apply(*l);
388 good_size = negUnlSizeTest(l, 2, false, true);
389 BEAST_EXPECT(good_size);
390 if (good_size)
391 {
392 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
393 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
394 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
395 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
396 }
397 }
398
399 {
400 //(9) ledgers before the next flag ledger
401 for (auto i = 0; i < 256; ++i)
402 {
403 auto good_size = negUnlSizeTest(l, 2, false, true);
404 BEAST_EXPECT(good_size);
405 if (good_size)
406 {
407 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
408 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
409 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
410 }
412 }
413 BEAST_EXPECT(l->isFlagLedger());
414 l->updateNegativeUNL();
415
416 //(10) next flag ledger
417 // test if the ledger updated correctly
418 auto good_size = negUnlSizeTest(l, 1, false, false);
419 BEAST_EXPECT(good_size);
420 if (good_size)
421 {
422 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
423 nUnlLedgerSeq.erase(publicKeys[0]);
424 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
425 }
426
427 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
428
429 OpenView accum(&*l);
430 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, true));
431 accum.apply(*l);
432 good_size = negUnlSizeTest(l, 1, false, true);
433 BEAST_EXPECT(good_size);
434 if (good_size)
435 {
436 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
437 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
438 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
439 }
440 }
441
442 {
443 //(11) ledgers before the next flag ledger
444 for (auto i = 0; i < 256; ++i)
445 {
446 auto good_size = negUnlSizeTest(l, 1, false, true);
447 BEAST_EXPECT(good_size);
448 if (good_size)
449 {
450 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
451 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
452 }
454 }
455 BEAST_EXPECT(l->isFlagLedger());
456 l->updateNegativeUNL();
457
458 //(12) next flag ledger
459 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
460 }
461
462 {
463 //(13) ledgers before the next flag ledger
464 for (auto i = 0; i < 256; ++i)
465 {
466 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
468 }
469 BEAST_EXPECT(l->isFlagLedger());
470 l->updateNegativeUNL();
471
472 //(14) next flag ledger
473 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
474 }
475 }
476
477 void
478 run() override
479 {
481 }
482};
483
488{
496 {
497 std::uint32_t numNodes; // number of validators
498 std::uint32_t negUNLSize; // size of negative UNL in the last ledger
499 bool hasToDisable; // if has ToDisable in the last ledger
500 bool hasToReEnable; // if has ToReEnable in the last ledger
506 };
507
509 : env(suite, jtx::testable_amendments()), param(p), validations(env.app().getValidations())
510 {
511 createNodes();
512 if (!param.numLedgers)
513 param.numLedgers = 256 * (param.negUNLSize + 1);
515 }
516
517 void
519 {
520 assert(param.numNodes <= 256);
522 for (int i = 0; i < param.numNodes; ++i)
523 {
524 UNLKeySet.insert(UNLKeys[i]);
525 UNLNodeIDs.push_back(calcNodeID(UNLKeys[i]));
526 UNLNodeIDSet.insert(UNLNodeIDs.back());
527 }
528 }
529
534 bool
536 {
537 static uint256 fake_amendment; // So we have different genesis ledgers
541
542 // When putting validators into the negative UNL, we start with
543 // validator 0, then validator 1 ...
544 int nidx = 0;
545 while (l->seq() <= param.numLedgers)
546 {
549
550 if (l->isFlagLedger())
551 {
552 l->updateNegativeUNL();
553 OpenView accum(&*l);
554 if (l->negativeUNL().size() < param.negUNLSize)
555 {
556 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
557 if (!applyAndTestResult(env, accum, tx, true))
558 break;
559 ++nidx;
560 }
561 else if (l->negativeUNL().size() == param.negUNLSize)
562 {
564 {
565 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
566 if (!applyAndTestResult(env, accum, tx, true))
567 break;
568 ++nidx;
569 }
571 {
572 auto tx = createTx(false, l->seq(), UNLKeys[0]);
573 if (!applyAndTestResult(env, accum, tx, true))
574 break;
575 }
576 }
577 accum.apply(*l);
578 }
579 l->updateSkipList();
580 }
582 }
583
592 {
593 static auto keyPair = randomKeyPair(KeyType::secp256k1);
595 env.app().timeKeeper().now(), keyPair.first, keyPair.second, v, [&](STValidation& v) {
596 v.setFieldH256(sfLedgerHash, ledger->header().hash);
597 v.setFieldU32(sfLedgerSequence, ledger->seq());
598 v.setFlag(vfFullValidation);
599 });
600 };
601
609 template <class NeedValidation>
610 void
611 walkHistoryAndAddValidations(NeedValidation&& needVal)
612 {
613 std::uint32_t curr = 0;
614 std::size_t need = 256 + 1;
615 // only last 256 + 1 ledgers need validations
616 if (history.size() > need)
617 curr = history.size() - need;
618 for (; curr != history.size(); ++curr)
619 {
620 for (std::size_t i = 0; i < param.numNodes; ++i)
621 {
622 if (needVal(history[curr], i))
623 {
625 v.setTrusted();
627 }
628 }
629 }
630 }
631
634 {
635 return history.back();
636 }
637
647};
648
649auto defaultPreVote = [](NegativeUNLVote& vote) {};
661template <typename PreVote = decltype(defaultPreVote)>
662bool
663voteAndCheck(NetworkHistory& history, NodeID const& myId, std::size_t expect, PreVote const& pre = defaultPreVote)
664{
665 NegativeUNLVote vote(myId, history.env.journal);
666 pre(vote);
668 vote.doVoting(history.lastLedger(), history.UNLKeySet, history.validations, txSet);
669 return countTx(txSet) == expect;
670}
671
676{
677 void
679 {
680 testcase("Create UNLModify Tx");
681 jtx::Env env(*this);
682
683 NodeID myId(0xA0);
684 NegativeUNLVote vote(myId, env.journal);
685
686 // one add, one remove
690 LedgerIndex seq(1234);
691 BEAST_EXPECT(countTx(txSet) == 0);
692 vote.addTx(seq, toDisableKey, NegativeUNLVote::ToDisable, txSet);
693 BEAST_EXPECT(countTx(txSet) == 1);
694 vote.addTx(seq, toReEnableKey, NegativeUNLVote::ToReEnable, txSet);
695 BEAST_EXPECT(countTx(txSet) == 2);
696 // content of a tx is implicitly tested after applied to a ledger
697 // in later test cases
698 }
699
700 void
702 {
703 testcase("Pick One Candidate");
704 jtx::Env env(*this);
705
706 NodeID myId(0xA0);
707 NegativeUNLVote vote(myId, env.journal);
708
709 uint256 pad_0(0);
710 uint256 pad_f = ~pad_0;
711 NodeID n_1(1);
712 NodeID n_2(2);
713 NodeID n_3(3);
714 std::vector<NodeID> candidates({n_1});
715 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
716 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_1);
717 candidates.emplace_back(2);
718 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
719 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_2);
720 candidates.emplace_back(3);
721 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
722 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_3);
723 }
724
725 void
727 {
728 testcase("Build Score Table");
729 /*
730 * 1. no skip list
731 * 2. short skip list
732 * 3. local node not enough history
733 * 4. a node double validated some seq
734 * 5. local node had enough validations but on a wrong chain
735 * 6. a good case, long enough history and perfect scores
736 */
737 {
738 // 1. no skip list
739 NetworkHistory history = {*this, {10, 0, false, false, 1}};
740 BEAST_EXPECT(history.goodHistory);
741 if (history.goodHistory)
742 {
743 NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal);
744 BEAST_EXPECT(!vote.buildScoreTable(history.lastLedger(), history.UNLNodeIDSet, history.validations));
745 }
746 }
747
748 {
749 // 2. short skip list
750 NetworkHistory history = {*this, {10, 0, false, false, 256 / 2}};
751 BEAST_EXPECT(history.goodHistory);
752 if (history.goodHistory)
753 {
754 NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal);
755 BEAST_EXPECT(!vote.buildScoreTable(history.lastLedger(), history.UNLNodeIDSet, history.validations));
756 }
757 }
758
759 {
760 // 3. local node not enough history
761 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
762 BEAST_EXPECT(history.goodHistory);
763 if (history.goodHistory)
764 {
765 NodeID myId = history.UNLNodeIDs[3];
767 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
768 // skip half my validations.
769 return !(history.UNLNodeIDs[idx] == myId && l->seq() % 2 == 0);
770 });
771 NegativeUNLVote vote(myId, history.env.journal);
772 BEAST_EXPECT(!vote.buildScoreTable(history.lastLedger(), history.UNLNodeIDSet, history.validations));
773 }
774 }
775
776 {
777 // 4. a node double validated some seq
778 // 5. local node had enough validations but on a wrong chain
779 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
780 // We need two chains for these tests
781 bool wrongChainSuccess = history.goodHistory;
782 BEAST_EXPECT(wrongChainSuccess);
783 NetworkHistory::LedgerHistory wrongChain = std::move(history.history);
784 // Create a new chain and use it as the one that majority of nodes
785 // follow
786 history.createLedgerHistory();
787 BEAST_EXPECT(history.goodHistory);
788
789 if (history.goodHistory && wrongChainSuccess)
790 {
791 NodeID myId = history.UNLNodeIDs[3];
792 NodeID badNode = history.UNLNodeIDs[4];
794 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
795 // everyone but me
796 return !(history.UNLNodeIDs[idx] == myId);
797 });
798
799 // local node validate wrong chain
800 // a node double validates
801 for (auto& l : wrongChain)
802 {
803 RCLValidation v1(history.createSTVal(l, myId));
804 history.validations.add(myId, v1);
805 RCLValidation v2(history.createSTVal(l, badNode));
806 history.validations.add(badNode, v2);
807 }
808
809 NegativeUNLVote vote(myId, history.env.journal);
810
811 // local node still on wrong chain, can build a scoreTable,
812 // but all other nodes' scores are zero
813 auto scoreTable = vote.buildScoreTable(wrongChain.back(), history.UNLNodeIDSet, history.validations);
814 BEAST_EXPECT(scoreTable);
815 if (scoreTable)
816 {
817 for (auto const& [n, score] : *scoreTable)
818 {
819 if (n == myId)
820 BEAST_EXPECT(score == 256);
821 else
822 BEAST_EXPECT(score == 0);
823 }
824 }
825
826 // if local node switched to right history, but cannot build
827 // scoreTable because not enough local validations
828 BEAST_EXPECT(!vote.buildScoreTable(history.lastLedger(), history.UNLNodeIDSet, history.validations));
829 }
830 }
831
832 {
833 // 6. a good case
834 NetworkHistory history = {*this, {10, 0, false, false, 256 + 1}};
835 BEAST_EXPECT(history.goodHistory);
836 if (history.goodHistory)
837 {
839 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return true; });
840 NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal);
841 auto scoreTable = vote.buildScoreTable(history.lastLedger(), history.UNLNodeIDSet, history.validations);
842 BEAST_EXPECT(scoreTable);
843 if (scoreTable)
844 {
845 for (auto const& [_, score] : *scoreTable)
846 {
847 (void)_;
848 BEAST_EXPECT(score == 256);
849 }
850 }
851 }
852 }
853 }
854
867 bool
869 NegativeUNLVote& vote,
870 hash_set<NodeID> const& unl,
871 hash_set<NodeID> const& negUnl,
872 hash_map<NodeID, std::uint32_t> const& scoreTable,
873 std::size_t numDisable,
874 std::size_t numReEnable)
875 {
876 auto [disableCandidates, reEnableCandidates] = vote.findAllCandidates(unl, negUnl, scoreTable);
877 bool rightDisable = disableCandidates.size() == numDisable;
878 bool rightReEnable = reEnableCandidates.size() == numReEnable;
879 return rightDisable && rightReEnable;
880 };
881
882 void
884 {
885 testcase("Find All Candidates");
886 /*
887 * -- unl size: 35
888 * -- negUnl size: 3
889 *
890 * 0. all good scores
891 * 1. all bad scores
892 * 2. all between watermarks
893 * 3. 2 good scorers in negUnl
894 * 4. 2 bad scorers not in negUnl
895 * 5. 2 in negUnl but not in unl, have a remove candidate from score
896 * table
897 * 6. 2 in negUnl but not in unl, no remove candidate from score table
898 * 7. 2 new validators have good scores, already in negUnl
899 * 8. 2 new validators have bad scores, not in negUnl
900 * 9. expired the new validators have bad scores, not in negUnl
901 */
902 NetworkHistory history = {*this, {35, 0, false, false, 0}};
903
904 hash_set<NodeID> negUnl_012;
905 for (std::uint32_t i = 0; i < 3; ++i)
906 negUnl_012.insert(history.UNLNodeIDs[i]);
907
908 // build a good scoreTable to use, or copy and modify
909 hash_map<NodeID, std::uint32_t> goodScoreTable;
910 for (auto const& n : history.UNLNodeIDs)
911 goodScoreTable[n] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
912
913 NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal);
914
915 {
916 // all good scores
917 BEAST_EXPECT(checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, goodScoreTable, 0, 3));
918 }
919 {
920 // all bad scores
922 for (auto& n : history.UNLNodeIDs)
924 BEAST_EXPECT(checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 35 - 3, 0));
925 }
926 {
927 // all between watermarks
929 for (auto& n : history.UNLNodeIDs)
931 BEAST_EXPECT(checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 0));
932 }
933
934 {
935 // 2 good scorers in negUnl
936 auto scoreTable = goodScoreTable;
937 scoreTable[*negUnl_012.begin()] = NegativeUNLVote::negativeUNLLowWaterMark + 1;
938 BEAST_EXPECT(checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 2));
939 }
940
941 {
942 // 2 bad scorers not in negUnl
943 auto scoreTable = goodScoreTable;
944 scoreTable[history.UNLNodeIDs[11]] = NegativeUNLVote::negativeUNLLowWaterMark - 1;
945 scoreTable[history.UNLNodeIDs[12]] = NegativeUNLVote::negativeUNLLowWaterMark - 1;
946 BEAST_EXPECT(checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 2, 3));
947 }
948
949 {
950 // 2 in negUnl but not in unl, have a remove candidate from score
951 // table
952 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
953 UNL_temp.erase(history.UNLNodeIDs[0]);
954 UNL_temp.erase(history.UNLNodeIDs[1]);
955 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, goodScoreTable, 0, 3));
956 }
957
958 {
959 // 2 in negUnl but not in unl, no remove candidate from score table
960 auto scoreTable = goodScoreTable;
961 scoreTable.erase(history.UNLNodeIDs[0]);
962 scoreTable.erase(history.UNLNodeIDs[1]);
963 scoreTable[history.UNLNodeIDs[2]] = NegativeUNLVote::negativeUNLLowWaterMark + 1;
964 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
965 UNL_temp.erase(history.UNLNodeIDs[0]);
966 UNL_temp.erase(history.UNLNodeIDs[1]);
967 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, scoreTable, 0, 2));
968 }
969
970 {
971 // 2 new validators
972 NodeID new_1(0xbead);
973 NodeID new_2(0xbeef);
974 hash_set<NodeID> nowTrusted = {new_1, new_2};
975 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
976 UNL_temp.insert(new_1);
977 UNL_temp.insert(new_2);
978 vote.newValidators(256, nowTrusted);
979 {
980 // 2 new validators have good scores, already in negUnl
981 auto scoreTable = goodScoreTable;
982 scoreTable[new_1] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
983 scoreTable[new_2] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
984 hash_set<NodeID> negUnl_temp = negUnl_012;
985 negUnl_temp.insert(new_1);
986 negUnl_temp.insert(new_2);
987 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_temp, scoreTable, 0, 3 + 2));
988 }
989 {
990 // 2 new validators have bad scores, not in negUnl
991 auto scoreTable = goodScoreTable;
992 scoreTable[new_1] = 0;
993 scoreTable[new_2] = 0;
994 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, scoreTable, 0, 3));
995 }
996 {
997 // expired the new validators have bad scores, not in negUnl
999 auto scoreTable = goodScoreTable;
1000 scoreTable[new_1] = 0;
1001 scoreTable[new_2] = 0;
1002 BEAST_EXPECT(checkCandidateSizes(vote, UNL_temp, negUnl_012, scoreTable, 2, 3));
1003 }
1004 }
1005 }
1006
1007 void
1009 {
1010 testcase("Find All Candidates Combination");
1011 /*
1012 * == combination 1:
1013 * -- unl size: 34, 35, 80
1014 * -- nUnl size: 0, 50%, all
1015 * -- score pattern: all 0, all negativeUNLLowWaterMark & +1 & -1, all
1016 * negativeUNLHighWaterMark & +1 & -1, all 100%
1017 *
1018 * == combination 2:
1019 * -- unl size: 34, 35, 80
1020 * -- negativeUNL size: 0, all
1021 * -- nUnl size: one on, one off, one on, one off,
1022 * -- score pattern: 2*(negativeUNLLowWaterMark, +1, -1) &
1023 * 2*(negativeUNLHighWaterMark, +1, -1) & rest
1024 * negativeUNLMinLocalValsToVote
1025 */
1026
1027 jtx::Env env(*this);
1028
1029 NodeID myId(0xA0);
1030 NegativeUNLVote vote(myId, env.journal);
1031
1032 std::array<std::uint32_t, 3> unlSizes = {34, 35, 80};
1033 std::array<std::uint32_t, 3> nUnlPercent = {0, 50, 100};
1035 0,
1043
1044 //== combination 1:
1045 {
1046 auto fillScoreTable = [&](std::uint32_t unl_size,
1047 std::uint32_t nUnl_size,
1048 std::uint32_t score,
1049 hash_set<NodeID>& unl,
1050 hash_set<NodeID>& negUnl,
1051 hash_map<NodeID, std::uint32_t>& scoreTable) {
1052 std::vector<NodeID> nodeIDs;
1054 for (auto const& k : keys)
1055 {
1056 nodeIDs.emplace_back(calcNodeID(k));
1057 unl.emplace(nodeIDs.back());
1058 scoreTable[nodeIDs.back()] = score;
1059 }
1060 for (std::uint32_t i = 0; i < nUnl_size; ++i)
1061 negUnl.insert(nodeIDs[i]);
1062 };
1063
1064 for (auto us : unlSizes)
1065 {
1066 for (auto np : nUnlPercent)
1067 {
1068 for (auto score : scores)
1069 {
1070 hash_set<NodeID> unl;
1071 hash_set<NodeID> negUnl;
1073 fillScoreTable(us, us * np / 100, score, unl, negUnl, scoreTable);
1074 BEAST_EXPECT(unl.size() == us);
1075 BEAST_EXPECT(negUnl.size() == us * np / 100);
1076 BEAST_EXPECT(scoreTable.size() == us);
1077
1078 std::size_t toDisable_expect = 0;
1079 std::size_t toReEnable_expect = 0;
1080 if (np == 0)
1081 {
1083 {
1084 toDisable_expect = us;
1085 }
1086 }
1087 else if (np == 50)
1088 {
1090 {
1091 toReEnable_expect = us * np / 100;
1092 }
1093 }
1094 else
1095 {
1097 {
1098 toReEnable_expect = us;
1099 }
1100 }
1101 BEAST_EXPECT(
1102 checkCandidateSizes(vote, unl, negUnl, scoreTable, toDisable_expect, toReEnable_expect));
1103 }
1104 }
1105 }
1106
1107 //== combination 2:
1108 {
1109 auto fillScoreTable = [&](std::uint32_t unl_size,
1110 std::uint32_t nUnl_percent,
1111 hash_set<NodeID>& unl,
1112 hash_set<NodeID>& negUnl,
1113 hash_map<NodeID, std::uint32_t>& scoreTable) {
1114 std::vector<NodeID> nodeIDs;
1116 for (auto const& k : keys)
1117 {
1118 nodeIDs.emplace_back(calcNodeID(k));
1119 unl.emplace(nodeIDs.back());
1120 }
1121
1122 std::uint32_t nIdx = 0;
1123 for (auto score : scores)
1124 {
1125 scoreTable[nodeIDs[nIdx++]] = score;
1126 scoreTable[nodeIDs[nIdx++]] = score;
1127 }
1128 for (; nIdx < unl_size;)
1129 {
1130 scoreTable[nodeIDs[nIdx++]] = scores.back();
1131 }
1132
1133 if (nUnl_percent == 100)
1134 {
1135 negUnl = unl;
1136 }
1137 else if (nUnl_percent == 50)
1138 {
1139 for (std::uint32_t i = 1; i < unl_size; i += 2)
1140 negUnl.insert(nodeIDs[i]);
1141 }
1142 };
1143
1144 for (auto us : unlSizes)
1145 {
1146 for (auto np : nUnlPercent)
1147 {
1148 hash_set<NodeID> unl;
1149 hash_set<NodeID> negUnl;
1151
1152 fillScoreTable(us, np, unl, negUnl, scoreTable);
1153 BEAST_EXPECT(unl.size() == us);
1154 BEAST_EXPECT(negUnl.size() == us * np / 100);
1155 BEAST_EXPECT(scoreTable.size() == us);
1156
1157 std::size_t toDisable_expect = 0;
1158 std::size_t toReEnable_expect = 0;
1159 if (np == 0)
1160 {
1161 toDisable_expect = 4;
1162 }
1163 else if (np == 50)
1164 {
1165 toReEnable_expect = negUnl.size() - 6;
1166 }
1167 else
1168 {
1169 toReEnable_expect = negUnl.size() - 12;
1170 }
1171 BEAST_EXPECT(
1172 checkCandidateSizes(vote, unl, negUnl, scoreTable, toDisable_expect, toReEnable_expect));
1173 }
1174 }
1175 }
1176 }
1177 }
1178
1179 void
1181 {
1182 testcase("New Validators");
1183 jtx::Env env(*this);
1184
1185 NodeID myId(0xA0);
1186 NegativeUNLVote vote(myId, env.journal);
1187
1188 // test cases:
1189 // newValidators_ of the NegativeUNLVote empty, add one
1190 // add a new one and one already added
1191 // add a new one and some already added
1192 // purge and see some are expired
1193
1194 NodeID n1(0xA1);
1195 NodeID n2(0xA2);
1196 NodeID n3(0xA3);
1197
1198 vote.newValidators(2, {n1});
1199 BEAST_EXPECT(vote.newValidators_.size() == 1);
1200 if (vote.newValidators_.size() == 1)
1201 {
1202 BEAST_EXPECT(vote.newValidators_.begin()->first == n1);
1203 BEAST_EXPECT(vote.newValidators_.begin()->second == 2);
1204 }
1205
1206 vote.newValidators(3, {n1, n2});
1207 BEAST_EXPECT(vote.newValidators_.size() == 2);
1208 if (vote.newValidators_.size() == 2)
1209 {
1210 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1211 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1212 }
1213
1215 BEAST_EXPECT(vote.newValidators_.size() == 3);
1216 if (vote.newValidators_.size() == 3)
1217 {
1218 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1219 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1221 }
1222
1224 BEAST_EXPECT(vote.newValidators_.size() == 3);
1226 BEAST_EXPECT(vote.newValidators_.size() == 2);
1228 BEAST_EXPECT(vote.newValidators_.size() == 1);
1229 BEAST_EXPECT(vote.newValidators_.begin()->first == n3);
1230 BEAST_EXPECT(vote.newValidators_.begin()->second == NegativeUNLVote::newValidatorDisableSkip);
1231 }
1232
1233 void
1243};
1244
1251{
1252 void
1254 {
1255 testcase("Build Score Table Combination");
1256 /*
1257 * local node good history, correct scores:
1258 * == combination:
1259 * -- unl size: 10, 34, 35, 50
1260 * -- score pattern: all 0, all 50%, all 100%, two 0% two 50% rest 100%
1261 */
1262 std::array<std::uint32_t, 4> unlSizes = {10, 34, 35, 50};
1263 std::array<std::array<std::uint32_t, 3>, 4> scorePattern = {
1264 {{{0, 0, 0}}, {{50, 50, 50}}, {{100, 100, 100}}, {{0, 50, 100}}}};
1265
1266 for (auto unlSize : unlSizes)
1267 {
1268 for (std::uint32_t sp = 0; sp < 4; ++sp)
1269 {
1270 NetworkHistory history = {*this, {unlSize, 0, false, false, 256 + 2}};
1271 BEAST_EXPECT(history.goodHistory);
1272 if (history.goodHistory)
1273 {
1274 NodeID myId = history.UNLNodeIDs[3];
1276 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1277 std::size_t k;
1278 if (idx < 2)
1279 k = 0;
1280 else if (idx < 4)
1281 k = 1;
1282 else
1283 k = 2;
1284
1285 bool add_50 = scorePattern[sp][k] == 50 && l->seq() % 2 == 0;
1286 bool add_100 = scorePattern[sp][k] == 100;
1287 bool add_me = history.UNLNodeIDs[idx] == myId;
1288 return add_50 || add_100 || add_me;
1289 });
1290
1291 NegativeUNLVote vote(myId, history.env.journal);
1292 auto scoreTable =
1293 vote.buildScoreTable(history.lastLedger(), history.UNLNodeIDSet, history.validations);
1294 BEAST_EXPECT(scoreTable);
1295 if (scoreTable)
1296 {
1297 std::uint32_t i = 0; // looping unl
1298 auto checkScores = [&](std::uint32_t score, std::uint32_t k) -> bool {
1299 if (history.UNLNodeIDs[i] == myId)
1300 return score == 256;
1301 if (scorePattern[sp][k] == 0)
1302 return score == 0;
1303 if (scorePattern[sp][k] == 50)
1304 return score == 256 / 2;
1305 if (scorePattern[sp][k] == 100)
1306 return score == 256;
1307 else
1308 return false;
1309 };
1310 for (; i < 2; ++i)
1311 {
1312 BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 0));
1313 }
1314 for (; i < 4; ++i)
1315 {
1316 BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 1));
1317 }
1318 for (; i < unlSize; ++i)
1319 {
1320 BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 2));
1321 }
1322 }
1323 }
1324 }
1325 }
1326 }
1327
1328 void
1329 run() override
1330 {
1332 }
1333};
1334
1335/*
1336 * Test the doVoting function of NegativeUNLVote.
1337 * The test cases are split to 5 classes for parallel execution.
1338 *
1339 * Voting tests: (use hasToDisable and hasToReEnable in some of the cases)
1340 *
1341 * == all good score, nUnl empty
1342 * -- txSet.size = 0
1343 * == all good score, nUnl not empty (use hasToDisable)
1344 * -- txSet.size = 1
1345 *
1346 * == 2 nodes offline, nUnl empty (use hasToReEnable)
1347 * -- txSet.size = 1
1348 * == 2 nodes offline, in nUnl
1349 * -- txSet.size = 0
1350 *
1351 * == 2 nodes offline, not in nUnl, but maxListed
1352 * -- txSet.size = 0
1353 *
1354 * == 2 nodes offline including me, not in nUnl
1355 * -- txSet.size = 0
1356 * == 2 nodes offline, not in negativeUNL, but I'm not a validator
1357 * -- txSet.size = 0
1358 * == 2 in nUnl, but not in unl, no other remove candidates
1359 * -- txSet.size = 1
1360 *
1361 * == 2 new validators have bad scores
1362 * -- txSet.size = 0
1363 * == 2 expired new validators have bad scores
1364 * -- txSet.size = 1
1365 */
1366
1368{
1369 void
1371 {
1372 testcase("Do Voting");
1373
1374 {
1375 //== all good score, negativeUNL empty
1376 //-- txSet.size = 0
1377 NetworkHistory history = {*this, {51, 0, false, false, {}}};
1378 BEAST_EXPECT(history.goodHistory);
1379 if (history.goodHistory)
1380 {
1382 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return true; });
1383 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1384 }
1385 }
1386
1387 {
1388 // all good score, negativeUNL not empty (use hasToDisable)
1389 //-- txSet.size = 1
1390 NetworkHistory history = {*this, {37, 0, true, false, {}}};
1391 BEAST_EXPECT(history.goodHistory);
1392 if (history.goodHistory)
1393 {
1395 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return true; });
1396 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 1));
1397 }
1398 }
1399 }
1400
1401 void
1402 run() override
1403 {
1404 testDoVoting();
1405 }
1406};
1407
1409{
1410 void
1412 {
1413 testcase("Do Voting");
1414
1415 {
1416 //== 2 nodes offline, negativeUNL empty (use hasToReEnable)
1417 //-- txSet.size = 1
1418 NetworkHistory history = {*this, {29, 1, false, true, {}}};
1419 BEAST_EXPECT(history.goodHistory);
1420 if (history.goodHistory)
1421 {
1423 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1424 // skip node 0 and node 1
1425 return idx > 1;
1426 });
1427 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 1));
1428 }
1429 }
1430
1431 {
1432 // 2 nodes offline, in negativeUNL
1433 //-- txSet.size = 0
1434 NetworkHistory history = {*this, {30, 1, true, false, {}}};
1435 BEAST_EXPECT(history.goodHistory);
1436 if (history.goodHistory)
1437 {
1438 NodeID n1 = calcNodeID(*history.lastLedger()->negativeUNL().begin());
1439 NodeID n2 = calcNodeID(*history.lastLedger()->validatorToDisable());
1441 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1442 // skip node 0 and node 1
1443 return history.UNLNodeIDs[idx] != n1 && history.UNLNodeIDs[idx] != n2;
1444 });
1445 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1446 }
1447 }
1448 }
1449
1450 void
1451 run() override
1452 {
1453 testDoVoting();
1454 }
1455};
1456
1458{
1459 void
1461 {
1462 testcase("Do Voting");
1463
1464 {
1465 // 2 nodes offline, not in negativeUNL, but maxListed
1466 //-- txSet.size = 0
1467 NetworkHistory history = {*this, {32, 8, true, true, {}}};
1468 BEAST_EXPECT(history.goodHistory);
1469 if (history.goodHistory)
1470 {
1472 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1473 // skip node 0 ~ 10
1474 return idx > 10;
1475 });
1476 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1477 }
1478 }
1479 }
1480
1481 void
1482 run() override
1483 {
1484 testDoVoting();
1485 }
1486};
1487
1489{
1490 void
1492 {
1493 testcase("Do Voting");
1494
1495 {
1496 //== 2 nodes offline including me, not in negativeUNL
1497 //-- txSet.size = 0
1498 NetworkHistory history = {*this, {35, 0, false, false, {}}};
1499 BEAST_EXPECT(history.goodHistory);
1500 if (history.goodHistory)
1501 {
1503 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return idx > 1; });
1504 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1505 }
1506 }
1507
1508 {
1509 // 2 nodes offline, not in negativeUNL, but I'm not a validator
1510 //-- txSet.size = 0
1511 NetworkHistory history = {*this, {40, 0, false, false, {}}};
1512 BEAST_EXPECT(history.goodHistory);
1513 if (history.goodHistory)
1514 {
1516 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return idx > 1; });
1517 BEAST_EXPECT(voteAndCheck(history, NodeID(0xdeadbeef), 0));
1518 }
1519 }
1520
1521 {
1522 //== 2 in negativeUNL, but not in unl, no other remove candidates
1523 //-- txSet.size = 1
1524 NetworkHistory history = {*this, {25, 2, false, false, {}}};
1525 BEAST_EXPECT(history.goodHistory);
1526 if (history.goodHistory)
1527 {
1529 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return idx > 1; });
1530 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 1, [&](NegativeUNLVote& vote) {
1531 history.UNLKeySet.erase(history.UNLKeys[0]);
1532 history.UNLKeySet.erase(history.UNLKeys[1]);
1533 }));
1534 }
1535 }
1536 }
1537
1538 void
1539 run() override
1540 {
1541 testDoVoting();
1542 }
1543};
1544
1546{
1547 void
1549 {
1550 testcase("Do Voting");
1551
1552 {
1553 //== 2 new validators have bad scores
1554 //-- txSet.size = 0
1555 NetworkHistory history = {*this, {15, 0, false, false, {}}};
1556 BEAST_EXPECT(history.goodHistory);
1557 if (history.goodHistory)
1558 {
1560 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return true; });
1561 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0, [&](NegativeUNLVote& vote) {
1562 auto extra_key_1 = randomKeyPair(KeyType::ed25519).first;
1563 auto extra_key_2 = randomKeyPair(KeyType::ed25519).first;
1564 history.UNLKeySet.insert(extra_key_1);
1565 history.UNLKeySet.insert(extra_key_2);
1566 hash_set<NodeID> nowTrusted;
1567 nowTrusted.insert(calcNodeID(extra_key_1));
1568 nowTrusted.insert(calcNodeID(extra_key_2));
1569 vote.newValidators(history.lastLedger()->seq(), nowTrusted);
1570 }));
1571 }
1572 }
1573
1574 {
1575 //== 2 expired new validators have bad scores
1576 //-- txSet.size = 1
1577 NetworkHistory history = {*this, {21, 0, false, false, NegativeUNLVote::newValidatorDisableSkip * 2}};
1578 BEAST_EXPECT(history.goodHistory);
1579 if (history.goodHistory)
1580 {
1582 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool { return true; });
1583 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 1, [&](NegativeUNLVote& vote) {
1584 auto extra_key_1 = randomKeyPair(KeyType::ed25519).first;
1585 auto extra_key_2 = randomKeyPair(KeyType::ed25519).first;
1586 history.UNLKeySet.insert(extra_key_1);
1587 history.UNLKeySet.insert(extra_key_2);
1588 hash_set<NodeID> nowTrusted;
1589 nowTrusted.insert(calcNodeID(extra_key_1));
1590 nowTrusted.insert(calcNodeID(extra_key_2));
1591 vote.newValidators(256, nowTrusted);
1592 }));
1593 }
1594 }
1595 }
1596
1597 void
1598 run() override
1599 {
1600 testDoVoting();
1601 }
1602};
1603
1605{
1606 void
1608 {
1609 testcase("Filter Validations");
1610 jtx::Env env(*this);
1611 auto l = std::make_shared<Ledger>(
1613
1614 auto createSTVal = [&](std::pair<PublicKey, SecretKey> const& keys) {
1616 env.app().timeKeeper().now(), keys.first, keys.second, calcNodeID(keys.first), [&](STValidation& v) {
1617 v.setFieldH256(sfLedgerHash, l->header().hash);
1618 v.setFieldU32(sfLedgerSequence, l->seq());
1619 v.setFlag(vfFullValidation);
1620 });
1621 };
1622
1623 // create keys and validations
1624 std::uint32_t numNodes = 10;
1625 std::uint32_t negUnlSize = 3;
1627 hash_set<NodeID> activeValidators;
1628 hash_set<PublicKey> nUnlKeys;
1630 for (int i = 0; i < numNodes; ++i)
1631 {
1632 auto keyPair = randomKeyPair(KeyType::secp256k1);
1633 vals.emplace_back(createSTVal(keyPair));
1634 cfgKeys.push_back(toBase58(TokenType::NodePublic, keyPair.first));
1635 activeValidators.emplace(calcNodeID(keyPair.first));
1636 if (i < negUnlSize)
1637 {
1638 nUnlKeys.insert(keyPair.first);
1639 }
1640 }
1641
1642 // setup the ValidatorList
1643 auto& validators = env.app().validators();
1644 auto& local = *nUnlKeys.begin();
1645 std::vector<std::string> cfgPublishers;
1646 validators.load(local, cfgKeys, cfgPublishers);
1647 validators.updateTrusted(
1648 activeValidators,
1649 env.timeKeeper().now(),
1650 env.app().getOPs(),
1651 env.app().overlay(),
1652 env.app().getHashRouter());
1653 BEAST_EXPECT(validators.getTrustedMasterKeys().size() == numNodes);
1654 validators.setNegativeUNL(nUnlKeys);
1655 BEAST_EXPECT(validators.getNegativeUNL().size() == negUnlSize);
1656
1657 // test the filter
1658 BEAST_EXPECT(vals.size() == numNodes);
1659 vals = validators.negativeUNLFilter(std::move(vals));
1660 BEAST_EXPECT(vals.size() == numNodes - negUnlSize);
1661 }
1662
1663 void
1664 run() override
1665 {
1667 }
1668};
1669
1670BEAST_DEFINE_TESTSUITE(NegativeUNL, consensus, xrpl);
1671
1672BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, xrpl);
1673BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, xrpl);
1674BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteGoodScore, consensus, xrpl, 1);
1675BEAST_DEFINE_TESTSUITE(NegativeUNLVoteOffline, consensus, xrpl);
1676BEAST_DEFINE_TESTSUITE(NegativeUNLVoteMaxListed, consensus, xrpl);
1677BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteRetiredValidator, consensus, xrpl, 1);
1678BEAST_DEFINE_TESTSUITE(NegativeUNLVoteNewValidator, consensus, xrpl);
1679BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, xrpl);
1680
1684bool
1685negUnlSizeTest(std::shared_ptr<Ledger const> const& l, size_t size, bool hasToDisable, bool hasToReEnable)
1686{
1687 bool sameSize = l->negativeUNL().size() == size;
1688 bool sameToDisable = (l->validatorToDisable() != std::nullopt) == hasToDisable;
1689 bool sameToReEnable = (l->validatorToReEnable() != std::nullopt) == hasToReEnable;
1690
1691 return sameSize && sameToDisable && sameToReEnable;
1692}
1693
1694bool
1695applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
1696{
1697 auto const res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
1698 if (pass)
1699 return res.ter == tesSUCCESS;
1700 else
1701 return res.ter == tefFAILURE || res.ter == temDISABLED;
1702}
1703
1704bool
1706{
1707 auto sle = l->read(keylet::negativeUNL());
1708 if (!sle)
1709 return false;
1710 if (!sle->isFieldPresent(sfDisabledValidators))
1711 return false;
1712
1713 auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
1714 if (nUnlData.size() != nUnlLedgerSeq.size())
1715 return false;
1716
1717 for (auto const& n : nUnlData)
1718 {
1719 if (!n.isFieldPresent(sfFirstLedgerSequence) || !n.isFieldPresent(sfPublicKey))
1720 return false;
1721
1722 auto seq = n.getFieldU32(sfFirstLedgerSequence);
1723 auto d = n.getFieldVL(sfPublicKey);
1724 auto s = makeSlice(d);
1725 if (!publicKeyType(s))
1726 return false;
1727 PublicKey pk(s);
1728 auto it = nUnlLedgerSeq.find(pk);
1729 if (it == nUnlLedgerSeq.end())
1730 return false;
1731 if (it->second != seq)
1732 return false;
1733 nUnlLedgerSeq.erase(it);
1734 }
1735 return nUnlLedgerSeq.size() == 0;
1736}
1737
1740{
1741 std::size_t count = 0;
1742 for (auto i = txSet->begin(); i != txSet->end(); ++i)
1743 {
1744 ++count;
1745 }
1746 return count;
1747};
1748
1751{
1753 std::size_t ss = 33;
1755 data[0] = 0xED;
1756 for (int i = 0; i < n; ++i)
1757 {
1758 data[1]++;
1759 Slice s(data.data(), ss);
1760 keys.emplace_back(s);
1761 }
1762 return keys;
1763}
1764
1765STTx
1766createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
1767{
1768 auto fill = [&](auto& obj) {
1769 obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
1770 obj.setFieldU32(sfLedgerSequence, seq);
1771 obj.setFieldVL(sfUNLModifyValidator, txKey);
1772 };
1773 return STTx(ttUNL_MODIFY, fill);
1774}
1775
1776} // namespace test
1777} // 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:147
virtual Config & config()=0
Manager to create NegativeUNL votes.
static constexpr size_t negativeUNLHighWaterMark
An unreliable validator must have more than negativeUNLHighWaterMark validations in the last flag led...
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
static constexpr size_t negativeUNLMinLocalValsToVote
The minimum number of validations of the local node for it to participate in the voting.
NodeID choose(uint256 const &randomPadData, std::vector< NodeID > const &candidates)
Pick one candidate from a vector of candidates.
void purgeNewValidators(LedgerIndex seq)
Purge validators that are not new anymore.
static constexpr size_t negativeUNLLowWaterMark
A validator is considered unreliable if its validations is less than negativeUNLLowWaterMark in the l...
std::optional< hash_map< NodeID, std::uint32_t > > buildScoreTable(std::shared_ptr< Ledger const > const &prevLedger, hash_set< NodeID > const &unl, RCLValidations &validations)
Build a reliability measurement score table of validators' validation messages in the last flag ledge...
static constexpr size_t newValidatorDisableSkip
We don't want to disable new validators immediately after adding them.
void doVoting(std::shared_ptr< Ledger const > const &prevLedger, hash_set< PublicKey > const &unlKeys, RCLValidations &validations, std::shared_ptr< SHAMap > const &initialSet)
Cast our local vote on the NegativeUNL candidates.
hash_map< NodeID, LedgerIndex > newValidators_
Candidates const findAllCandidates(hash_set< NodeID > const &unl, hash_set< NodeID > const &negUnl, hash_map< NodeID, std::uint32_t > const &scoreTable)
Process the score table and find all disabling and re-enabling candidates.
void addTx(LedgerIndex seq, PublicKey const &vp, NegativeUNLModify modify, std::shared_ptr< SHAMap > const &initialSet)
Add a ttUNL_MODIFY Tx to the transaction set.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:45
void apply(TxsRawView &to) const
Apply changes.
Definition OpenView.cpp:101
A public key.
Definition PublicKey.h:42
Wrapper over STValidation for generic Validation code.
virtual ValidatorList & validators()=0
virtual NetworkOPs & getOPs()=0
virtual Overlay & overlay()=0
virtual HashRouter & getHashRouter()=0
virtual Family & getNodeFamily()=0
virtual TimeKeeper & timeKeeper()=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:43
time_point closeTime() const
Returns the predicted close time, in network time.
Definition TimeKeeper.h:55
ValStatus add(NodeID const &nodeID, Validation const &val)
Add a new validation.
time_point now() const override
Returns the current time.
Test the private member functions of NegativeUNLVote.
bool checkCandidateSizes(NegativeUNLVote &vote, hash_set< NodeID > const &unl, hash_set< NodeID > const &negUnl, hash_map< NodeID, std::uint32_t > const &scoreTable, std::size_t numDisable, std::size_t numReEnable)
Find all candidates and check if the number of candidates meets expectation.
Rest the build score table function of NegativeUNLVote.
void testNegativeUNL()
Test filling and applying ttUNL_MODIFY Tx, as well as ledger update:
void run() override
Runs the suite.
A transaction testing environment.
Definition Env.h:119
Application & app()
Definition Env.h:251
ManualTimeKeeper & timeKeeper()
Definition Env.h:263
beast::Journal const journal
Definition Env.h:160
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:201
auto const data
General field definitions, or fields used in multiple transaction namespaces.
FeatureBitset testable_amendments()
Definition Env.h:76
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(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:117
@ tefFAILURE
Definition TER.h:146
create_genesis_t const create_genesis
Definition Ledger.cpp:32
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:254
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
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:213
@ tesSUCCESS
Definition TER.h:225
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