xrpld
Loading...
Searching...
No Matches
NegativeUNL_test.cpp
1
2#include <test/jtx/Env.h>
3
4#include <xrpld/app/consensus/RCLValidations.h>
5#include <xrpld/app/misc/NegativeUNLVote.h>
6#include <xrpld/app/misc/ValidatorList.h>
7
8#include <xrpl/basics/Slice.h>
9#include <xrpl/basics/UnorderedContainers.h>
10#include <xrpl/basics/base_uint.h>
11#include <xrpl/beast/unit_test/suite.h>
12#include <xrpl/ledger/ApplyView.h>
13#include <xrpl/ledger/Ledger.h>
14#include <xrpl/ledger/OpenView.h>
15#include <xrpl/protocol/Indexes.h>
16#include <xrpl/protocol/KeyType.h>
17#include <xrpl/protocol/Protocol.h>
18#include <xrpl/protocol/PublicKey.h>
19#include <xrpl/protocol/SField.h>
20#include <xrpl/protocol/STTx.h>
21#include <xrpl/protocol/STValidation.h>
22#include <xrpl/protocol/SecretKey.h>
23#include <xrpl/protocol/TER.h>
24#include <xrpl/protocol/TxFormats.h>
25#include <xrpl/protocol/UintTypes.h>
26#include <xrpl/protocol/tokens.h>
27#include <xrpl/shamap/SHAMapMissingNode.h>
28#include <xrpl/tx/apply.h>
29
30#include <array>
31#include <cassert>
32#include <cstddef>
33#include <cstdint>
34#include <memory>
35#include <optional>
36#include <utility>
37#include <vector>
38
39namespace xrpl::test {
40
41/*
42 * This file implements the following negative UNL related tests:
43 * -- test filling and applying ttUNL_MODIFY Tx and ledger update
44 * -- test the NegativeUNLVote class. The test cases are split to multiple
45 * test classes to allow parallel execution.
46 * -- test the negativeUNLFilter function
47 *
48 * Other negative UNL related tests such as ValidatorList and RPC related ones
49 * are put in their existing unit test files.
50 */
51
62bool
64 std::shared_ptr<Ledger const> const& l,
65 size_t size,
66 bool hasToDisable,
67 bool hasToReEnable);
68
78bool
79applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
80
90bool
92 std::shared_ptr<Ledger const> const& l,
94
101std::size_t
102countTx(std::shared_ptr<SHAMap> const& txSet);
103
110std::vector<PublicKey>
111createPublicKeys(std::size_t n);
112
121STTx
122createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
123
125{
133 void
135 {
136 /*
137 * test cases:
138 *
139 * (1) the ledger after genesis
140 * -- cannot apply Disable Tx
141 * -- cannot apply ReEnable Tx
142 * -- nUNL empty
143 * -- no ToDisable
144 * -- no ToReEnable
145 *
146 * (2) a flag ledger
147 * -- apply an Disable Tx
148 * -- cannot apply the second Disable Tx
149 * -- cannot apply a ReEnable Tx
150 * -- nUNL empty
151 * -- has ToDisable with right nodeId
152 * -- no ToReEnable
153 * ++ extra test: first Disable Tx in ledger TxSet
154 *
155 * (3) ledgers before the next flag ledger
156 * -- nUNL empty
157 * -- has ToDisable with right nodeId
158 * -- no ToReEnable
159 *
160 * (4) next flag ledger
161 * -- nUNL size == 1, with right nodeId
162 * -- no ToDisable
163 * -- no ToReEnable
164 * -- cannot apply an Disable Tx with nodeId already in nUNL
165 * -- apply an Disable Tx with different nodeId
166 * -- cannot apply a ReEnable Tx with the same NodeId as Add
167 * -- cannot apply a ReEnable Tx with a NodeId not in nUNL
168 * -- apply a ReEnable Tx with a nodeId already in nUNL
169 * -- has ToDisable with right nodeId
170 * -- has ToReEnable with right nodeId
171 * -- nUNL size still 1, right nodeId
172 *
173 * (5) ledgers before the next flag ledger
174 * -- nUNL size == 1, right nodeId
175 * -- has ToDisable with right nodeId
176 * -- has ToReEnable with right nodeId
177 *
178 * (6) next flag ledger
179 * -- nUNL size == 1, different nodeId
180 * -- no ToDisable
181 * -- no ToReEnable
182 * -- apply an Disable Tx with different nodeId
183 * -- nUNL size still 1, right nodeId
184 * -- has ToDisable with right nodeId
185 * -- no ToReEnable
186 *
187 * (7) ledgers before the next flag ledger
188 * -- nUNL size still 1, right nodeId
189 * -- has ToDisable with right nodeId
190 * -- no ToReEnable
191 *
192 * (8) next flag ledger
193 * -- nUNL size == 2
194 * -- apply a ReEnable Tx
195 * -- cannot apply second ReEnable Tx, even with right nodeId
196 * -- cannot apply an Disable Tx with the same NodeId as Remove
197 * -- nUNL size == 2
198 * -- no ToDisable
199 * -- has ToReEnable with right nodeId
200 *
201 * (9) ledgers before the next flag ledger
202 * -- nUNL size == 2
203 * -- no ToDisable
204 * -- has ToReEnable with right nodeId
205 *
206 * (10) next flag ledger
207 * -- nUNL size == 1
208 * -- apply a ReEnable Tx
209 * -- nUNL size == 1
210 * -- no ToDisable
211 * -- has ToReEnable with right nodeId
212 *
213 * (11) ledgers before the next flag ledger
214 * -- nUNL size == 1
215 * -- no ToDisable
216 * -- has ToReEnable with right nodeId
217 *
218 * (12) next flag ledger
219 * -- nUNL size == 0
220 * -- no ToDisable
221 * -- no ToReEnable
222 *
223 * (13) ledgers before the next flag ledger
224 * -- nUNL size == 0
225 * -- no ToDisable
226 * -- no ToReEnable
227 *
228 * (14) next flag ledger
229 * -- nUNL size == 0
230 * -- no ToDisable
231 * -- no ToReEnable
232 */
233
234 testcase("Create UNLModify Tx and apply to ledgers");
235
236 jtx::Env env(*this, jtx::testableAmendments());
238 // genesis ledger
241 Rules{env.app().config().features},
242 env.app().config().fees.toFees(),
244 env.app().getNodeFamily());
245
246 // Record the public keys and ledger sequences of expected negative UNL
247 // validators when we build the ledger history
249
250 {
251 //(1) the ledger after genesis, not a flag ledger
253
254 auto txDisable0 = createTx(true, l->seq(), publicKeys[0]);
255 auto txReEnable1 = createTx(false, l->seq(), publicKeys[1]);
256
257 OpenView accum(&*l);
258 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable0, false));
259 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable1, false));
260 accum.apply(*l);
261 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
262 }
263
264 {
265 //(2) a flag ledger
266 // generate more ledgers
267 for (auto i = 0; i < 256 - 2; ++i)
268 {
270 }
271 BEAST_EXPECT(l->isFlagLedger());
272 l->updateNegativeUNL();
273
274 auto txDisable0 = createTx(true, l->seq(), publicKeys[0]);
275 auto txDisable1 = createTx(true, l->seq(), publicKeys[1]);
276 auto txReEnable2 = createTx(false, l->seq(), publicKeys[2]);
277
278 // can apply 1 and only 1 ToDisable Tx,
279 // cannot apply ToReEnable Tx, since negative UNL is empty
280 OpenView accum(&*l);
281 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable0, true));
282 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable1, false));
283 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable2, false));
284 accum.apply(*l);
285 auto goodSize = negUnlSizeTest(l, 0, true, false);
286 BEAST_EXPECT(goodSize);
287 if (goodSize)
288 {
289 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
290 //++ first ToDisable Tx in ledger's TxSet
291 uint256 const txID = txDisable0.getTransactionID();
292 BEAST_EXPECT(l->txExists(txID));
293 }
294 }
295
296 {
297 //(3) ledgers before the next flag ledger
298 for (auto i = 0; i < 256; ++i)
299 {
300 auto goodSize = negUnlSizeTest(l, 0, true, false);
301 BEAST_EXPECT(goodSize);
302 if (goodSize)
303 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
305 }
306 BEAST_EXPECT(l->isFlagLedger());
307 l->updateNegativeUNL();
308
309 //(4) next flag ledger
310 // test if the ledger updated correctly
311 auto goodSize = negUnlSizeTest(l, 1, false, false);
312 BEAST_EXPECT(goodSize);
313 if (goodSize)
314 {
315 BEAST_EXPECT(*(l->negativeUNL().begin()) == publicKeys[0]);
316 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
317 }
318
319 auto txDisable0 = createTx(true, l->seq(), publicKeys[0]);
320 auto txDisable1 = createTx(true, l->seq(), publicKeys[1]);
321 auto txReEnable0 = createTx(false, l->seq(), publicKeys[0]);
322 auto txReEnable1 = createTx(false, l->seq(), publicKeys[1]);
323 auto txReEnable2 = createTx(false, l->seq(), publicKeys[2]);
324
325 OpenView accum(&*l);
326 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable0, false));
327 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable1, true));
328 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable1, false));
329 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable2, false));
330 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable0, true));
331 accum.apply(*l);
332 goodSize = negUnlSizeTest(l, 1, true, true);
333 BEAST_EXPECT(goodSize);
334 if (goodSize)
335 {
336 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
337 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
338 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
339 // test sfFirstLedgerSequence
340 BEAST_EXPECT(verifyPubKeyAndSeq(l, nUnlLedgerSeq));
341 }
342 }
343
344 {
345 //(5) ledgers before the next flag ledger
346 for (auto i = 0; i < 256; ++i)
347 {
348 auto goodSize = negUnlSizeTest(l, 1, true, true);
349 BEAST_EXPECT(goodSize);
350 if (goodSize)
351 {
352 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
353 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
354 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
355 }
357 }
358 BEAST_EXPECT(l->isFlagLedger());
359 l->updateNegativeUNL();
360
361 //(6) next flag ledger
362 // test if the ledger updated correctly
363 auto goodSize = negUnlSizeTest(l, 1, false, false);
364 BEAST_EXPECT(goodSize);
365 if (goodSize)
366 {
367 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
368 }
369
370 auto txDisable0 = createTx(true, l->seq(), publicKeys[0]);
371
372 OpenView accum(&*l);
373 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable0, true));
374 accum.apply(*l);
375 goodSize = negUnlSizeTest(l, 1, true, false);
376 BEAST_EXPECT(goodSize);
377 if (goodSize)
378 {
379 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
380 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
381 nUnlLedgerSeq.emplace(publicKeys[1], l->seq());
382 nUnlLedgerSeq.erase(publicKeys[0]);
383 BEAST_EXPECT(verifyPubKeyAndSeq(l, nUnlLedgerSeq));
384 }
385 }
386
387 {
388 //(7) ledgers before the next flag ledger
389 for (auto i = 0; i < 256; ++i)
390 {
391 auto goodSize = negUnlSizeTest(l, 1, true, false);
392 BEAST_EXPECT(goodSize);
393 if (goodSize)
394 {
395 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
396 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
397 }
399 }
400 BEAST_EXPECT(l->isFlagLedger());
401 l->updateNegativeUNL();
402
403 //(8) next flag ledger
404 // test if the ledger updated correctly
405 auto goodSize = negUnlSizeTest(l, 2, false, false);
406 BEAST_EXPECT(goodSize);
407 if (goodSize)
408 {
409 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
410 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
411 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
412 BEAST_EXPECT(verifyPubKeyAndSeq(l, nUnlLedgerSeq));
413 }
414
415 auto txDisable0 = createTx(true, l->seq(), publicKeys[0]);
416 auto txReEnable0 = createTx(false, l->seq(), publicKeys[0]);
417 auto txReEnable1 = createTx(false, l->seq(), publicKeys[1]);
418
419 OpenView accum(&*l);
420 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable0, true));
421 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable1, false));
422 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable0, false));
423 accum.apply(*l);
424 goodSize = negUnlSizeTest(l, 2, false, true);
425 BEAST_EXPECT(goodSize);
426 if (goodSize)
427 {
428 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
429 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
430 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
431 BEAST_EXPECT(verifyPubKeyAndSeq(l, nUnlLedgerSeq));
432 }
433 }
434
435 {
436 //(9) ledgers before the next flag ledger
437 for (auto i = 0; i < 256; ++i)
438 {
439 auto goodSize = negUnlSizeTest(l, 2, false, true);
440 BEAST_EXPECT(goodSize);
441 if (goodSize)
442 {
443 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
444 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
445 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
446 }
448 }
449 BEAST_EXPECT(l->isFlagLedger());
450 l->updateNegativeUNL();
451
452 //(10) next flag ledger
453 // test if the ledger updated correctly
454 auto goodSize = negUnlSizeTest(l, 1, false, false);
455 BEAST_EXPECT(goodSize);
456 if (goodSize)
457 {
458 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
459 nUnlLedgerSeq.erase(publicKeys[0]);
460 BEAST_EXPECT(verifyPubKeyAndSeq(l, nUnlLedgerSeq));
461 }
462
463 auto txReEnable1 = createTx(false, l->seq(), publicKeys[1]);
464
465 OpenView accum(&*l);
466 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable1, true));
467 accum.apply(*l);
468 goodSize = negUnlSizeTest(l, 1, false, true);
469 BEAST_EXPECT(goodSize);
470 if (goodSize)
471 {
472 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
473 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
474 BEAST_EXPECT(verifyPubKeyAndSeq(l, nUnlLedgerSeq));
475 }
476 }
477
478 {
479 //(11) ledgers before the next flag ledger
480 for (auto i = 0; i < 256; ++i)
481 {
482 auto goodSize = negUnlSizeTest(l, 1, false, true);
483 BEAST_EXPECT(goodSize);
484 if (goodSize)
485 {
486 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
487 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
488 }
490 }
491 BEAST_EXPECT(l->isFlagLedger());
492 l->updateNegativeUNL();
493
494 //(12) next flag ledger
495 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
496 }
497
498 {
499 //(13) ledgers before the next flag ledger
500 for (auto i = 0; i < 256; ++i)
501 {
502 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
504 }
505 BEAST_EXPECT(l->isFlagLedger());
506 l->updateNegativeUNL();
507
508 //(14) next flag ledger
509 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
510 }
511 }
512
513 void
514 run() override
515 {
517 }
518};
519
524{
532 {
533 std::uint32_t numNodes; // number of validators
534 std::uint32_t negUNLSize; // size of negative UNL in the last ledger
535 bool hasToDisable; // if has ToDisable in the last ledger
536 bool hasToReEnable; // if has ToReEnable in the last ledger
542 };
543
545 : env(suite, jtx::testableAmendments()), param(p), validations(env.app().getValidations())
546 {
547 createNodes();
548 if (!param.numLedgers)
549 param.numLedgers = 256 * (param.negUNLSize + 1);
551 }
552
553 void
555 {
556 assert(param.numNodes <= 256);
557 unlKeys = createPublicKeys(param.numNodes);
558 for (int i = 0; i < param.numNodes; ++i)
559 {
560 unlKeySet.insert(unlKeys[i]);
561 unlNodeIDs.push_back(calcNodeID(unlKeys[i]));
562 unlNodeIdSet.insert(unlNodeIDs.back());
563 }
564 }
565
570 bool
572 {
573 static uint256 kFakeAmendment; // So we have different genesis ledgers
576 Rules{env.app().config().features},
577 env.app().config().fees.toFees(),
578 std::vector<uint256>{kFakeAmendment++},
579 env.app().getNodeFamily());
580 history.push_back(l);
581
582 // When putting validators into the negative UNL, we start with
583 // validator 0, then validator 1 ...
584 int nidx = 0;
585 while (l->seq() <= param.numLedgers)
586 {
587 l = std::make_shared<Ledger>(*l, env.app().getTimeKeeper().closeTime());
588 history.push_back(l);
589
590 if (l->isFlagLedger())
591 {
592 l->updateNegativeUNL();
593 OpenView accum(&*l);
594 if (l->negativeUNL().size() < param.negUNLSize)
595 {
596 auto tx = createTx(true, l->seq(), unlKeys[nidx]);
597 if (!applyAndTestResult(env, accum, tx, true))
598 break;
599 ++nidx;
600 }
601 else if (l->negativeUNL().size() == param.negUNLSize)
602 {
603 if (param.hasToDisable)
604 {
605 auto tx = createTx(true, l->seq(), unlKeys[nidx]);
606 if (!applyAndTestResult(env, accum, tx, true))
607 break;
608 ++nidx;
609 }
610 if (param.hasToReEnable)
611 {
612 auto tx = createTx(false, l->seq(), unlKeys[0]);
613 if (!applyAndTestResult(env, accum, tx, true))
614 break;
615 }
616 }
617 accum.apply(*l);
618 }
619 l->updateSkipList();
620 }
621 return negUnlSizeTest(l, param.negUNLSize, param.hasToDisable, param.hasToReEnable);
622 }
623
632 {
633 static auto kEyPair = randomKeyPair(KeyType::Secp256k1);
635 env.app().getTimeKeeper().now(),
636 kEyPair.first,
637 kEyPair.second,
638 v,
639 [&](STValidation& v) {
640 v.setFieldH256(sfLedgerHash, ledger->header().hash);
641 v.setFieldU32(sfLedgerSequence, ledger->seq());
642 v.setFlag(kVfFullValidation);
643 });
644 };
645
653 template <class NeedValidation>
654 void
655 walkHistoryAndAddValidations(NeedValidation&& needVal)
656 {
657 std::uint32_t curr = 0;
658 std::size_t const need = 256 + 1;
659 // only last 256 + 1 ledgers need validations
660 if (history.size() > need)
661 curr = history.size() - need;
662 for (; curr != history.size(); ++curr)
663 {
664 for (std::size_t i = 0; i < param.numNodes; ++i)
665 {
666 if (needVal(history[curr], i))
667 {
669 v.setTrusted();
670 validations.add(unlNodeIDs[i], v);
671 }
672 }
673 }
674 }
675
678 {
679 return history.back();
680 }
681
691};
692
705template <typename PreVote = decltype(gDefaultPreVote)>
706bool
708 NetworkHistory& history,
709 NodeID const& myId,
710 std::size_t expect,
711 PreVote const& pre = gDefaultPreVote)
712{
713 NegativeUNLVote vote(myId, history.env.journal);
714 pre(vote);
715 auto txSet =
717 vote.doVoting(history.lastLedger(), history.unlKeySet, history.validations, txSet);
718 return countTx(txSet) == expect;
719}
720
725{
726 void
728 {
729 testcase("Create UNLModify Tx");
730 jtx::Env env(*this);
731
732 NodeID const myId(0xA0);
733 NegativeUNLVote vote(myId, env.journal);
734
735 // one add, one remove
739 LedgerIndex const seq(1234);
740 BEAST_EXPECT(countTx(txSet) == 0);
741 vote.addTx(seq, toDisableKey, NegativeUNLVote::NegativeUNLModify::ToDisable, txSet);
742 BEAST_EXPECT(countTx(txSet) == 1);
743 vote.addTx(seq, toReEnableKey, NegativeUNLVote::NegativeUNLModify::ToReEnable, txSet);
744 BEAST_EXPECT(countTx(txSet) == 2);
745 // content of a tx is implicitly tested after applied to a ledger
746 // in later test cases
747 }
748
749 void
751 {
752 testcase("Pick One Candidate");
753 jtx::Env const env(*this);
754
755 NodeID const myId(0xA0);
756 NegativeUNLVote const vote(myId, env.journal);
757
758 uint256 const pad0(0);
759 uint256 const padF = ~pad0;
760 NodeID const n1(1);
761 NodeID const n2(2);
762 NodeID const n3(3);
763 std::vector<NodeID> candidates({n1});
764 BEAST_EXPECT(vote.choose(pad0, candidates) == n1);
765 BEAST_EXPECT(vote.choose(padF, candidates) == n1);
766 candidates.emplace_back(2);
767 BEAST_EXPECT(vote.choose(pad0, candidates) == n1);
768 BEAST_EXPECT(vote.choose(padF, candidates) == n2);
769 candidates.emplace_back(3);
770 BEAST_EXPECT(vote.choose(pad0, candidates) == n1);
771 BEAST_EXPECT(vote.choose(padF, candidates) == n3);
772 }
773
774 void
776 {
777 testcase("Build Score Table");
778 /*
779 * 1. no skip list
780 * 2. short skip list
781 * 3. local node not enough history
782 * 4. a node double validated some seq
783 * 5. local node had enough validations but on a wrong chain
784 * 6. a good case, long enough history and perfect scores
785 */
786 {
787 // 1. no skip list
788 NetworkHistory history = {
789 *this,
790 {.numNodes = 10,
791 .negUNLSize = 0,
792 .hasToDisable = false,
793 .hasToReEnable = false,
794 .numLedgers = 1}};
795 BEAST_EXPECT(history.goodHistory);
796 if (history.goodHistory)
797 {
798 NegativeUNLVote vote(history.unlNodeIDs[3], history.env.journal);
799 BEAST_EXPECT(!vote.buildScoreTable(
800 history.lastLedger(), history.unlNodeIdSet, history.validations));
801 }
802 }
803
804 {
805 // 2. short skip list
806 NetworkHistory history = {
807 *this,
808 {.numNodes = 10,
809 .negUNLSize = 0,
810 .hasToDisable = false,
811 .hasToReEnable = false,
812 .numLedgers = 256 / 2}};
813 BEAST_EXPECT(history.goodHistory);
814 if (history.goodHistory)
815 {
816 NegativeUNLVote vote(history.unlNodeIDs[3], history.env.journal);
817 BEAST_EXPECT(!vote.buildScoreTable(
818 history.lastLedger(), history.unlNodeIdSet, history.validations));
819 }
820 }
821
822 {
823 // 3. local node not enough history
824 NetworkHistory history = {
825 *this,
826 {.numNodes = 10,
827 .negUNLSize = 0,
828 .hasToDisable = false,
829 .hasToReEnable = false,
830 .numLedgers = 256 + 2}};
831 BEAST_EXPECT(history.goodHistory);
832 if (history.goodHistory)
833 {
834 NodeID myId = history.unlNodeIDs[3];
836 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
837 // skip half my validations.
838 return history.unlNodeIDs[idx] != myId || l->seq() % 2 != 0;
839 });
840 NegativeUNLVote vote(myId, history.env.journal);
841 BEAST_EXPECT(!vote.buildScoreTable(
842 history.lastLedger(), history.unlNodeIdSet, history.validations));
843 }
844 }
845
846 {
847 // 4. a node double validated some seq
848 // 5. local node had enough validations but on a wrong chain
849 NetworkHistory history = {
850 *this,
851 {.numNodes = 10,
852 .negUNLSize = 0,
853 .hasToDisable = false,
854 .hasToReEnable = false,
855 .numLedgers = 256 + 2}};
856 // We need two chains for these tests
857 bool const wrongChainSuccess = history.goodHistory;
858 BEAST_EXPECT(wrongChainSuccess);
859 NetworkHistory::LedgerHistory wrongChain = std::move(history.history);
860 // Create a new chain and use it as the one that majority of nodes
861 // follow
862 history.createLedgerHistory();
863 BEAST_EXPECT(history.goodHistory);
864
865 if (history.goodHistory && wrongChainSuccess)
866 {
867 NodeID myId = history.unlNodeIDs[3];
868 NodeID const badNode = history.unlNodeIDs[4];
870 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
871 // everyone but me
872 return !(history.unlNodeIDs[idx] == myId);
873 });
874
875 // local node validate wrong chain
876 // a node double validates
877 for (auto& l : wrongChain)
878 {
879 RCLValidation const v1(history.createSTVal(l, myId));
880 history.validations.add(myId, v1);
881 RCLValidation const v2(history.createSTVal(l, badNode));
882 history.validations.add(badNode, v2);
883 }
884
885 NegativeUNLVote vote(myId, history.env.journal);
886
887 // local node still on wrong chain, can build a scoreTable,
888 // but all other nodes' scores are zero
889 auto scoreTable = vote.buildScoreTable(
890 wrongChain.back(), history.unlNodeIdSet, history.validations);
891 BEAST_EXPECT(scoreTable);
892 if (scoreTable)
893 {
894 for (auto const& [n, score] : *scoreTable)
895 {
896 if (n == myId)
897 {
898 BEAST_EXPECT(score == 256);
899 }
900 else
901 {
902 BEAST_EXPECT(score == 0);
903 }
904 }
905 }
906
907 // if local node switched to right history, but cannot build
908 // scoreTable because not enough local validations
909 BEAST_EXPECT(!vote.buildScoreTable(
910 history.lastLedger(), history.unlNodeIdSet, history.validations));
911 }
912 }
913
914 {
915 // 6. a good case
916 NetworkHistory history = {
917 *this,
918 {.numNodes = 10,
919 .negUNLSize = 0,
920 .hasToDisable = false,
921 .hasToReEnable = false,
922 .numLedgers = 256 + 1}};
923 BEAST_EXPECT(history.goodHistory);
924 if (history.goodHistory)
925 {
927 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
928 return true;
929 });
930 NegativeUNLVote vote(history.unlNodeIDs[3], history.env.journal);
931 auto scoreTable = vote.buildScoreTable(
932 history.lastLedger(), history.unlNodeIdSet, history.validations);
933 BEAST_EXPECT(scoreTable);
934 if (scoreTable)
935 {
936 for (auto const& [_, score] : *scoreTable)
937 {
938 (void)_;
939 BEAST_EXPECT(score == 256);
940 }
941 }
942 }
943 }
944 }
945
958 static bool
960 NegativeUNLVote& vote,
961 hash_set<NodeID> const& unl,
962 hash_set<NodeID> const& negUnl,
963 hash_map<NodeID, std::uint32_t> const& scoreTable,
964 std::size_t numDisable,
965 std::size_t numReEnable)
966 {
967 auto [disableCandidates, reEnableCandidates] =
968 vote.findAllCandidates(unl, negUnl, scoreTable);
969 bool const rightDisable = disableCandidates.size() == numDisable;
970 bool const rightReEnable = reEnableCandidates.size() == numReEnable;
971 return rightDisable && rightReEnable;
972 };
973
974 void
976 {
977 testcase("Find All Candidates");
978 /*
979 * -- unl size: 35
980 * -- negUnl size: 3
981 *
982 * 0. all good scores
983 * 1. all bad scores
984 * 2. all between watermarks
985 * 3. 2 good scorers in negUnl
986 * 4. 2 bad scorers not in negUnl
987 * 5. 2 in negUnl but not in unl, have a remove candidate from score
988 * table
989 * 6. 2 in negUnl but not in unl, no remove candidate from score table
990 * 7. 2 new validators have good scores, already in negUnl
991 * 8. 2 new validators have bad scores, not in negUnl
992 * 9. expired the new validators have bad scores, not in negUnl
993 */
994 NetworkHistory history = {
995 *this,
996 {.numNodes = 35,
997 .negUNLSize = 0,
998 .hasToDisable = false,
999 .hasToReEnable = false,
1000 .numLedgers = 0}};
1001
1002 hash_set<NodeID> negUnl012;
1003 for (std::uint32_t i = 0; i < 3; ++i)
1004 negUnl012.insert(history.unlNodeIDs[i]);
1005
1006 // build a good scoreTable to use, or copy and modify
1007 hash_map<NodeID, std::uint32_t> goodScoreTable;
1008 for (auto const& n : history.unlNodeIDs)
1009 goodScoreTable[n] = NegativeUNLVote::kNegativeUnlHighWaterMark + 1;
1010
1011 NegativeUNLVote vote(history.unlNodeIDs[0], history.env.journal);
1012
1013 {
1014 // all good scores
1015 BEAST_EXPECT(
1016 checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, goodScoreTable, 0, 3));
1017 }
1018 {
1019 // all bad scores
1021 for (auto& n : history.unlNodeIDs)
1023 BEAST_EXPECT(
1024 checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 35 - 3, 0));
1025 }
1026 {
1027 // all between watermarks
1029 for (auto& n : history.unlNodeIDs)
1031 BEAST_EXPECT(
1032 checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 0, 0));
1033 }
1034
1035 {
1036 // 2 good scorers in negUnl
1037 auto scoreTable = goodScoreTable;
1038 scoreTable[*negUnl012.begin()] = NegativeUNLVote::kNegativeUnlLowWaterMark + 1;
1039 BEAST_EXPECT(
1040 checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 0, 2));
1041 }
1042
1043 {
1044 // 2 bad scorers not in negUnl
1045 auto scoreTable = goodScoreTable;
1046 scoreTable[history.unlNodeIDs[11]] = NegativeUNLVote::kNegativeUnlLowWaterMark - 1;
1047 scoreTable[history.unlNodeIDs[12]] = NegativeUNLVote::kNegativeUnlLowWaterMark - 1;
1048 BEAST_EXPECT(
1049 checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 2, 3));
1050 }
1051
1052 {
1053 // 2 in negUnl but not in unl, have a remove candidate from score
1054 // table
1055 hash_set<NodeID> unlTemp = history.unlNodeIdSet;
1056 unlTemp.erase(history.unlNodeIDs[0]);
1057 unlTemp.erase(history.unlNodeIDs[1]);
1058 BEAST_EXPECT(checkCandidateSizes(vote, unlTemp, negUnl012, goodScoreTable, 0, 3));
1059 }
1060
1061 {
1062 // 2 in negUnl but not in unl, no remove candidate from score table
1063 auto scoreTable = goodScoreTable;
1064 scoreTable.erase(history.unlNodeIDs[0]);
1065 scoreTable.erase(history.unlNodeIDs[1]);
1066 scoreTable[history.unlNodeIDs[2]] = NegativeUNLVote::kNegativeUnlLowWaterMark + 1;
1067 hash_set<NodeID> unlTemp = history.unlNodeIdSet;
1068 unlTemp.erase(history.unlNodeIDs[0]);
1069 unlTemp.erase(history.unlNodeIDs[1]);
1070 BEAST_EXPECT(checkCandidateSizes(vote, unlTemp, negUnl012, scoreTable, 0, 2));
1071 }
1072
1073 {
1074 // 2 new validators
1075 NodeID const new1(0xbead);
1076 NodeID const new2(0xbeef);
1077 hash_set<NodeID> const nowTrusted = {new1, new2};
1078 hash_set<NodeID> unlTemp = history.unlNodeIdSet;
1079 unlTemp.insert(new1);
1080 unlTemp.insert(new2);
1081 vote.newValidators(256, nowTrusted);
1082 {
1083 // 2 new validators have good scores, already in negUnl
1084 auto scoreTable = goodScoreTable;
1085 scoreTable[new1] = NegativeUNLVote::kNegativeUnlHighWaterMark + 1;
1086 scoreTable[new2] = NegativeUNLVote::kNegativeUnlHighWaterMark + 1;
1087 hash_set<NodeID> negUnlTemp = negUnl012;
1088 negUnlTemp.insert(new1);
1089 negUnlTemp.insert(new2);
1090 BEAST_EXPECT(checkCandidateSizes(vote, unlTemp, negUnlTemp, scoreTable, 0, 3 + 2));
1091 }
1092 {
1093 // 2 new validators have bad scores, not in negUnl
1094 auto scoreTable = goodScoreTable;
1095 scoreTable[new1] = 0;
1096 scoreTable[new2] = 0;
1097 BEAST_EXPECT(checkCandidateSizes(vote, unlTemp, negUnl012, scoreTable, 0, 3));
1098 }
1099 {
1100 // expired the new validators have bad scores, not in negUnl
1102 auto scoreTable = goodScoreTable;
1103 scoreTable[new1] = 0;
1104 scoreTable[new2] = 0;
1105 BEAST_EXPECT(checkCandidateSizes(vote, unlTemp, negUnl012, scoreTable, 2, 3));
1106 }
1107 }
1108 }
1109
1110 void
1112 {
1113 testcase("Find All Candidates Combination");
1114 /*
1115 * == combination 1:
1116 * -- unl size: 34, 35, 80
1117 * -- nUnl size: 0, 50%, all
1118 * -- score pattern: all 0, all negativeUNLLowWaterMark & +1 & -1, all
1119 * negativeUNLHighWaterMark & +1 & -1, all 100%
1120 *
1121 * == combination 2:
1122 * -- unl size: 34, 35, 80
1123 * -- negativeUNL size: 0, all
1124 * -- nUnl size: one on, one off, one on, one off,
1125 * -- score pattern: 2*(negativeUNLLowWaterMark, +1, -1) &
1126 * 2*(negativeUNLHighWaterMark, +1, -1) & rest
1127 * negativeUNLMinLocalValsToVote
1128 */
1129
1130 jtx::Env const env(*this);
1131
1132 NodeID const myId(0xA0);
1133 NegativeUNLVote vote(myId, env.journal);
1134
1135 std::array<std::uint32_t, 3> const unlSizes = {34, 35, 80};
1136 std::array<std::uint32_t, 3> const nUnlPercent = {0, 50, 100};
1138 0,
1146
1147 //== combination 1:
1148 {
1149 auto fillScoreTable = [&](std::uint32_t unlSize,
1150 std::uint32_t nUnlSize,
1151 std::uint32_t score,
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(unlSize);
1157 for (auto const& k : keys)
1158 {
1159 nodeIDs.emplace_back(calcNodeID(k));
1160 unl.emplace(nodeIDs.back());
1161 scoreTable[nodeIDs.back()] = score;
1162 }
1163 for (std::uint32_t i = 0; i < nUnlSize; ++i)
1164 negUnl.insert(nodeIDs[i]);
1165 };
1166
1167 for (auto us : unlSizes)
1168 {
1169 for (auto np : nUnlPercent)
1170 {
1171 for (auto score : scores)
1172 {
1173 hash_set<NodeID> unl;
1174 hash_set<NodeID> negUnl;
1176 fillScoreTable(us, us * np / 100, score, unl, negUnl, scoreTable);
1177 BEAST_EXPECT(unl.size() == us);
1178 BEAST_EXPECT(negUnl.size() == us * np / 100);
1179 BEAST_EXPECT(scoreTable.size() == us);
1180
1181 std::size_t toDisableExpect = 0;
1182 std::size_t toReEnableExpect = 0;
1183 if (np == 0)
1184 {
1186 {
1187 toDisableExpect = us;
1188 }
1189 }
1190 else if (np == 50)
1191 {
1193 {
1194 toReEnableExpect = us * np / 100;
1195 }
1196 }
1197 else
1198 {
1200 {
1201 toReEnableExpect = us;
1202 }
1203 }
1204 BEAST_EXPECT(checkCandidateSizes(
1205 vote, unl, negUnl, scoreTable, toDisableExpect, toReEnableExpect));
1206 }
1207 }
1208 }
1209
1210 //== combination 2:
1211 {
1212 auto fillScoreTable = [&](std::uint32_t unlSize,
1213 std::uint32_t nUnlPercent,
1214 hash_set<NodeID>& unl,
1215 hash_set<NodeID>& negUnl,
1216 hash_map<NodeID, std::uint32_t>& scoreTable) {
1217 std::vector<NodeID> nodeIDs;
1218 std::vector<PublicKey> const keys = createPublicKeys(unlSize);
1219 for (auto const& k : keys)
1220 {
1221 nodeIDs.emplace_back(calcNodeID(k));
1222 unl.emplace(nodeIDs.back());
1223 }
1224
1225 std::uint32_t nIdx = 0;
1226 for (auto score : scores)
1227 {
1228 scoreTable[nodeIDs[nIdx++]] = score;
1229 scoreTable[nodeIDs[nIdx++]] = score;
1230 }
1231 for (; nIdx < unlSize;)
1232 {
1233 scoreTable[nodeIDs[nIdx++]] = scores.back();
1234 }
1235
1236 if (nUnlPercent == 100)
1237 {
1238 negUnl = unl;
1239 }
1240 else if (nUnlPercent == 50)
1241 {
1242 for (std::uint32_t i = 1; i < unlSize; i += 2)
1243 negUnl.insert(nodeIDs[i]);
1244 }
1245 };
1246
1247 for (auto us : unlSizes)
1248 {
1249 for (auto np : nUnlPercent)
1250 {
1251 hash_set<NodeID> unl;
1252 hash_set<NodeID> negUnl;
1254
1255 fillScoreTable(us, np, unl, negUnl, scoreTable);
1256 BEAST_EXPECT(unl.size() == us);
1257 BEAST_EXPECT(negUnl.size() == us * np / 100);
1258 BEAST_EXPECT(scoreTable.size() == us);
1259
1260 std::size_t toDisableExpect = 0;
1261 std::size_t toReEnableExpect = 0;
1262 if (np == 0)
1263 {
1264 toDisableExpect = 4;
1265 }
1266 else if (np == 50)
1267 {
1268 toReEnableExpect = negUnl.size() - 6;
1269 }
1270 else
1271 {
1272 toReEnableExpect = negUnl.size() - 12;
1273 }
1274 BEAST_EXPECT(checkCandidateSizes(
1275 vote, unl, negUnl, scoreTable, toDisableExpect, toReEnableExpect));
1276 }
1277 }
1278 }
1279 }
1280 }
1281
1282 void
1284 {
1285 testcase("New Validators");
1286 jtx::Env const env(*this);
1287
1288 NodeID const myId(0xA0);
1289 NegativeUNLVote vote(myId, env.journal);
1290
1291 // test cases:
1292 // newValidators_ of the NegativeUNLVote empty, add one
1293 // add a new one and one already added
1294 // add a new one and some already added
1295 // purge and see some are expired
1296
1297 NodeID const n1(0xA1);
1298 NodeID const n2(0xA2);
1299 NodeID const n3(0xA3);
1300
1301 vote.newValidators(2, {n1});
1302 BEAST_EXPECT(vote.newValidators_.size() == 1);
1303 if (vote.newValidators_.size() == 1)
1304 {
1305 BEAST_EXPECT(vote.newValidators_.begin()->first == n1);
1306 BEAST_EXPECT(vote.newValidators_.begin()->second == 2);
1307 }
1308
1309 vote.newValidators(3, {n1, n2});
1310 BEAST_EXPECT(vote.newValidators_.size() == 2);
1311 if (vote.newValidators_.size() == 2)
1312 {
1313 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1314 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1315 }
1316
1318 BEAST_EXPECT(vote.newValidators_.size() == 3);
1319 if (vote.newValidators_.size() == 3)
1320 {
1321 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1322 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1324 }
1325
1327 BEAST_EXPECT(vote.newValidators_.size() == 3);
1329 BEAST_EXPECT(vote.newValidators_.size() == 2);
1331 BEAST_EXPECT(vote.newValidators_.size() == 1);
1332 BEAST_EXPECT(vote.newValidators_.begin()->first == n3);
1333 BEAST_EXPECT(
1335 }
1336
1337 void
1347};
1348
1355{
1356 void
1358 {
1359 testcase("Build Score Table Combination");
1360 /*
1361 * local node good history, correct scores:
1362 * == combination:
1363 * -- unl size: 10, 34, 35, 50
1364 * -- score pattern: all 0, all 50%, all 100%, two 0% two 50% rest 100%
1365 */
1366 std::array<std::uint32_t, 4> const unlSizes = {10, 34, 35, 50};
1367 std::array<std::array<std::uint32_t, 3>, 4> scorePattern = {
1368 {{{0, 0, 0}}, {{50, 50, 50}}, {{100, 100, 100}}, {{0, 50, 100}}}};
1369
1370 for (auto unlSize : unlSizes)
1371 {
1372 for (std::uint32_t sp = 0; sp < 4; ++sp)
1373 {
1374 NetworkHistory history = {
1375 *this,
1376 {.numNodes = unlSize,
1377 .negUNLSize = 0,
1378 .hasToDisable = false,
1379 .hasToReEnable = false,
1380 .numLedgers = 256 + 2}};
1381 BEAST_EXPECT(history.goodHistory);
1382 if (history.goodHistory)
1383 {
1384 NodeID myId = history.unlNodeIDs[3];
1386 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1387 std::size_t k = 0;
1388 if (idx < 2)
1389 {
1390 k = 0;
1391 }
1392 else if (idx < 4)
1393 {
1394 k = 1;
1395 }
1396 else
1397 {
1398 k = 2;
1399 }
1400
1401 bool const add50 = scorePattern[sp][k] == 50 && l->seq() % 2 == 0;
1402 bool const add100 = scorePattern[sp][k] == 100;
1403 bool const addMe = history.unlNodeIDs[idx] == myId;
1404 return add50 || add100 || addMe;
1405 });
1406
1407 NegativeUNLVote vote(myId, history.env.journal);
1408 auto scoreTable = vote.buildScoreTable(
1409 history.lastLedger(), history.unlNodeIdSet, history.validations);
1410 BEAST_EXPECT(scoreTable);
1411 if (scoreTable)
1412 {
1413 std::uint32_t i = 0; // looping unl
1414 auto checkScores = [&](std::uint32_t score, std::uint32_t k) -> bool {
1415 if (history.unlNodeIDs[i] == myId)
1416 return score == 256;
1417 if (scorePattern[sp][k] == 0)
1418 return score == 0;
1419 if (scorePattern[sp][k] == 50)
1420 return score == 256 / 2;
1421 if (scorePattern[sp][k] == 100)
1422 {
1423 return score == 256;
1424 }
1425
1426 return false;
1427 };
1428 for (; i < 2; ++i)
1429 {
1430 BEAST_EXPECT(checkScores((*scoreTable)[history.unlNodeIDs[i]], 0));
1431 }
1432 for (; i < 4; ++i)
1433 {
1434 BEAST_EXPECT(checkScores((*scoreTable)[history.unlNodeIDs[i]], 1));
1435 }
1436 for (; i < unlSize; ++i)
1437 {
1438 BEAST_EXPECT(checkScores((*scoreTable)[history.unlNodeIDs[i]], 2));
1439 }
1440 }
1441 }
1442 }
1443 }
1444 }
1445
1446 void
1447 run() override
1448 {
1450 }
1451};
1452
1453/*
1454 * Test the doVoting function of NegativeUNLVote.
1455 * The test cases are split to 5 classes for parallel execution.
1456 *
1457 * Voting tests: (use hasToDisable and hasToReEnable in some of the cases)
1458 *
1459 * == all good score, nUnl empty
1460 * -- txSet.size = 0
1461 * == all good score, nUnl not empty (use hasToDisable)
1462 * -- txSet.size = 1
1463 *
1464 * == 2 nodes offline, nUnl empty (use hasToReEnable)
1465 * -- txSet.size = 1
1466 * == 2 nodes offline, in nUnl
1467 * -- txSet.size = 0
1468 *
1469 * == 2 nodes offline, not in nUnl, but maxListed
1470 * -- txSet.size = 0
1471 *
1472 * == 2 nodes offline including me, not in nUnl
1473 * -- txSet.size = 0
1474 * == 2 nodes offline, not in negativeUNL, but I'm not a validator
1475 * -- txSet.size = 0
1476 * == 2 in nUnl, but not in unl, no other remove candidates
1477 * -- txSet.size = 1
1478 *
1479 * == 2 new validators have bad scores
1480 * -- txSet.size = 0
1481 * == 2 expired new validators have bad scores
1482 * -- txSet.size = 1
1483 */
1484
1486{
1487 void
1489 {
1490 testcase("Do Voting");
1491
1492 {
1493 //== all good score, negativeUNL empty
1494 //-- txSet.size = 0
1495 NetworkHistory history = {
1496 *this,
1497 {.numNodes = 51,
1498 .negUNLSize = 0,
1499 .hasToDisable = false,
1500 .hasToReEnable = false,
1501 .numLedgers = {}}};
1502 BEAST_EXPECT(history.goodHistory);
1503 if (history.goodHistory)
1504 {
1506 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1507 return true;
1508 });
1509 BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs[0], 0));
1510 }
1511 }
1512
1513 {
1514 // all good score, negativeUNL not empty (use hasToDisable)
1515 //-- txSet.size = 1
1516 NetworkHistory history = {
1517 *this,
1518 {.numNodes = 37,
1519 .negUNLSize = 0,
1520 .hasToDisable = true,
1521 .hasToReEnable = false,
1522 .numLedgers = {}}};
1523 BEAST_EXPECT(history.goodHistory);
1524 if (history.goodHistory)
1525 {
1527 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1528 return true;
1529 });
1530 BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs[0], 1));
1531 }
1532 }
1533 }
1534
1535 void
1536 run() override
1537 {
1538 testDoVoting();
1539 }
1540};
1541
1543{
1544 void
1546 {
1547 testcase("Do Voting");
1548
1549 {
1550 //== 2 nodes offline, negativeUNL empty (use hasToReEnable)
1551 //-- txSet.size = 1
1552 NetworkHistory history = {
1553 *this,
1554 {.numNodes = 29,
1555 .negUNLSize = 1,
1556 .hasToDisable = false,
1557 .hasToReEnable = true,
1558 .numLedgers = {}}};
1559 BEAST_EXPECT(history.goodHistory);
1560 if (history.goodHistory)
1561 {
1563 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1564 // skip node 0 and node 1
1565 return idx > 1;
1566 });
1567 BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs.back(), 1));
1568 }
1569 }
1570
1571 {
1572 // 2 nodes offline, in negativeUNL
1573 //-- txSet.size = 0
1574 NetworkHistory history = {
1575 *this,
1576 {.numNodes = 30,
1577 .negUNLSize = 1,
1578 .hasToDisable = true,
1579 .hasToReEnable = false,
1580 .numLedgers = {}}};
1581 BEAST_EXPECT(history.goodHistory);
1582 if (history.goodHistory)
1583 {
1584 NodeID n1 = calcNodeID(*history.lastLedger()->negativeUNL().begin());
1585 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1586 NodeID n2 = calcNodeID(*history.lastLedger()->validatorToDisable());
1588 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1589 // skip node 0 and node 1
1590 return history.unlNodeIDs[idx] != n1 && history.unlNodeIDs[idx] != n2;
1591 });
1592 BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs.back(), 0));
1593 }
1594 }
1595 }
1596
1597 void
1598 run() override
1599 {
1600 testDoVoting();
1601 }
1602};
1603
1605{
1606 void
1608 {
1609 testcase("Do Voting");
1610
1611 {
1612 // 2 nodes offline, not in negativeUNL, but maxListed
1613 //-- txSet.size = 0
1614 NetworkHistory history = {
1615 *this,
1616 {.numNodes = 32,
1617 .negUNLSize = 8,
1618 .hasToDisable = true,
1619 .hasToReEnable = true,
1620 .numLedgers = {}}};
1621 BEAST_EXPECT(history.goodHistory);
1622 if (history.goodHistory)
1623 {
1625 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1626 // skip node 0 ~ 10
1627 return idx > 10;
1628 });
1629 BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs.back(), 0));
1630 }
1631 }
1632 }
1633
1634 void
1635 run() override
1636 {
1637 testDoVoting();
1638 }
1639};
1640
1642{
1643 void
1645 {
1646 testcase("Do Voting");
1647
1648 {
1649 //== 2 nodes offline including me, not in negativeUNL
1650 //-- txSet.size = 0
1651 NetworkHistory history = {
1652 *this,
1653 {.numNodes = 35,
1654 .negUNLSize = 0,
1655 .hasToDisable = false,
1656 .hasToReEnable = false,
1657 .numLedgers = {}}};
1658 BEAST_EXPECT(history.goodHistory);
1659 if (history.goodHistory)
1660 {
1662 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1663 return idx > 1;
1664 });
1665 BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs[0], 0));
1666 }
1667 }
1668
1669 {
1670 // 2 nodes offline, not in negativeUNL, but I'm not a validator
1671 //-- txSet.size = 0
1672 NetworkHistory history = {
1673 *this,
1674 {.numNodes = 40,
1675 .negUNLSize = 0,
1676 .hasToDisable = false,
1677 .hasToReEnable = false,
1678 .numLedgers = {}}};
1679 BEAST_EXPECT(history.goodHistory);
1680 if (history.goodHistory)
1681 {
1683 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1684 return idx > 1;
1685 });
1686 BEAST_EXPECT(voteAndCheck(history, NodeID(0xdeadbeef), 0));
1687 }
1688 }
1689
1690 {
1691 //== 2 in negativeUNL, but not in unl, no other remove candidates
1692 //-- txSet.size = 1
1693 NetworkHistory history = {
1694 *this,
1695 {.numNodes = 25,
1696 .negUNLSize = 2,
1697 .hasToDisable = false,
1698 .hasToReEnable = false,
1699 .numLedgers = {}}};
1700 BEAST_EXPECT(history.goodHistory);
1701 if (history.goodHistory)
1702 {
1704 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1705 return idx > 1;
1706 });
1707 BEAST_EXPECT(
1708 voteAndCheck(history, history.unlNodeIDs.back(), 1, [&](NegativeUNLVote& vote) {
1709 history.unlKeySet.erase(history.unlKeys[0]);
1710 history.unlKeySet.erase(history.unlKeys[1]);
1711 }));
1712 }
1713 }
1714 }
1715
1716 void
1717 run() override
1718 {
1719 testDoVoting();
1720 }
1721};
1722
1724{
1725 void
1727 {
1728 testcase("Do Voting");
1729
1730 {
1731 //== 2 new validators have bad scores
1732 //-- txSet.size = 0
1733 NetworkHistory history = {
1734 *this,
1735 {.numNodes = 15,
1736 .negUNLSize = 0,
1737 .hasToDisable = false,
1738 .hasToReEnable = false,
1739 .numLedgers = {}}};
1740 BEAST_EXPECT(history.goodHistory);
1741 if (history.goodHistory)
1742 {
1744 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1745 return true;
1746 });
1747 BEAST_EXPECT(
1748 voteAndCheck(history, history.unlNodeIDs[0], 0, [&](NegativeUNLVote& vote) {
1749 auto extraKey1 = randomKeyPair(KeyType::Ed25519).first;
1750 auto extraKey2 = randomKeyPair(KeyType::Ed25519).first;
1751 history.unlKeySet.insert(extraKey1);
1752 history.unlKeySet.insert(extraKey2);
1753 hash_set<NodeID> nowTrusted;
1754 nowTrusted.insert(calcNodeID(extraKey1));
1755 nowTrusted.insert(calcNodeID(extraKey2));
1756 vote.newValidators(history.lastLedger()->seq(), nowTrusted);
1757 }));
1758 }
1759 }
1760
1761 {
1762 //== 2 expired new validators have bad scores
1763 //-- txSet.size = 1
1764 NetworkHistory history = {
1765 *this,
1766 {.numNodes = 21,
1767 .negUNLSize = 0,
1768 .hasToDisable = false,
1769 .hasToReEnable = false,
1771 BEAST_EXPECT(history.goodHistory);
1772 if (history.goodHistory)
1773 {
1775 [&](std::shared_ptr<Ledger const> const& l, std::size_t idx) -> bool {
1776 return true;
1777 });
1778 BEAST_EXPECT(
1779 voteAndCheck(history, history.unlNodeIDs[0], 1, [&](NegativeUNLVote& vote) {
1780 auto extraKey1 = randomKeyPair(KeyType::Ed25519).first;
1781 auto extraKey2 = randomKeyPair(KeyType::Ed25519).first;
1782 history.unlKeySet.insert(extraKey1);
1783 history.unlKeySet.insert(extraKey2);
1784 hash_set<NodeID> nowTrusted;
1785 nowTrusted.insert(calcNodeID(extraKey1));
1786 nowTrusted.insert(calcNodeID(extraKey2));
1787 vote.newValidators(256, nowTrusted);
1788 }));
1789 }
1790 }
1791 }
1792
1793 void
1794 run() override
1795 {
1796 testDoVoting();
1797 }
1798};
1799
1801{
1802 void
1804 {
1805 testcase("Filter Validations");
1806 jtx::Env env(*this);
1807 auto l = std::make_shared<Ledger>(
1809 Rules{env.app().config().features},
1810 env.app().config().fees.toFees(),
1812 env.app().getNodeFamily());
1813
1814 auto createSTVal = [&](std::pair<PublicKey, SecretKey> const& keys) {
1816 env.app().getTimeKeeper().now(),
1817 keys.first,
1818 keys.second,
1819 calcNodeID(keys.first),
1820 [&](STValidation& v) {
1821 v.setFieldH256(sfLedgerHash, l->header().hash);
1822 v.setFieldU32(sfLedgerSequence, l->seq());
1823 v.setFlag(kVfFullValidation);
1824 });
1825 };
1826
1827 // create keys and validations
1828 std::uint32_t const numNodes = 10;
1829 std::uint32_t const negUnlSize = 3;
1831 hash_set<NodeID> activeValidators;
1832 hash_set<PublicKey> nUnlKeys;
1834 for (int i = 0; i < numNodes; ++i)
1835 {
1836 auto keyPair = randomKeyPair(KeyType::Secp256k1);
1837 vals.emplace_back(createSTVal(keyPair));
1838 cfgKeys.push_back(toBase58(TokenType::NodePublic, keyPair.first));
1839 activeValidators.emplace(calcNodeID(keyPair.first));
1840 if (i < negUnlSize)
1841 {
1842 nUnlKeys.insert(keyPair.first);
1843 }
1844 }
1845
1846 // setup the ValidatorList
1847 auto& validators = env.app().getValidators();
1848 auto& local = *nUnlKeys.begin();
1849 std::vector<std::string> const cfgPublishers;
1850 validators.load(local, cfgKeys, cfgPublishers);
1851 validators.updateTrusted(
1852 activeValidators,
1853 env.timeKeeper().now(),
1854 env.app().getOPs(),
1855 env.app().getOverlay(),
1856 env.app().getHashRouter());
1857 BEAST_EXPECT(validators.getTrustedMasterKeys().size() == numNodes);
1858 validators.setNegativeUNL(nUnlKeys);
1859 BEAST_EXPECT(validators.getNegativeUNL().size() == negUnlSize);
1860
1861 // test the filter
1862 BEAST_EXPECT(vals.size() == numNodes);
1863 vals = validators.negativeUNLFilter(std::move(vals));
1864 BEAST_EXPECT(vals.size() == numNodes - negUnlSize);
1865 }
1866
1867 void
1868 run() override
1869 {
1871 }
1872};
1873
1874BEAST_DEFINE_TESTSUITE(NegativeUNL, consensus, xrpl);
1875
1876BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, xrpl);
1877BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, xrpl);
1878BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteGoodScore, consensus, xrpl, 1);
1879BEAST_DEFINE_TESTSUITE(NegativeUNLVoteOffline, consensus, xrpl);
1880BEAST_DEFINE_TESTSUITE(NegativeUNLVoteMaxListed, consensus, xrpl);
1881BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteRetiredValidator, consensus, xrpl, 1);
1882BEAST_DEFINE_TESTSUITE(NegativeUNLVoteNewValidator, consensus, xrpl);
1883BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, xrpl);
1884
1888bool
1891 size_t size,
1892 bool hasToDisable,
1893 bool hasToReEnable)
1894{
1895 bool const sameSize = l->negativeUNL().size() == size;
1896 bool const sameToDisable = (l->validatorToDisable() != std::nullopt) == hasToDisable;
1897 bool const sameToReEnable = (l->validatorToReEnable() != std::nullopt) == hasToReEnable;
1898
1899 return sameSize && sameToDisable && sameToReEnable;
1900}
1901
1902bool
1903applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
1904{
1905 auto const res = apply(env.app(), view, tx, ApplyFlags::TapNone, env.journal);
1906 if (pass)
1907 {
1908 return isTesSuccess(res.ter);
1909 }
1910
1911 return res.ter == tefFAILURE || res.ter == temDISABLED;
1912}
1913
1914bool
1918{
1919 auto sle = l->read(keylet::negativeUNL());
1920 if (!sle)
1921 return false;
1922 if (!sle->isFieldPresent(sfDisabledValidators))
1923 return false;
1924
1925 auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
1926 if (nUnlData.size() != nUnlLedgerSeq.size())
1927 return false;
1928
1929 for (auto const& n : nUnlData)
1930 {
1931 if (!n.isFieldPresent(sfFirstLedgerSequence) || !n.isFieldPresent(sfPublicKey))
1932 return false;
1933
1934 auto seq = n.getFieldU32(sfFirstLedgerSequence);
1935 auto d = n.getFieldVL(sfPublicKey);
1936 auto s = makeSlice(d);
1937 if (!publicKeyType(s))
1938 return false;
1939 PublicKey const pk(s);
1940 auto it = nUnlLedgerSeq.find(pk);
1941 if (it == nUnlLedgerSeq.end())
1942 return false;
1943 if (it->second != seq)
1944 return false;
1945 nUnlLedgerSeq.erase(it);
1946 }
1947 return nUnlLedgerSeq.empty();
1948}
1949
1952{
1953 std::size_t count = 0;
1954 for (auto i = txSet->begin(); i != txSet->end(); ++i)
1955 {
1956 ++count;
1957 }
1958 return count;
1959};
1960
1963{
1965 std::size_t const ss = 33;
1966 std::vector<uint8_t> data(ss, 0);
1967 data[0] = 0xED;
1968 for (int i = 0; i < n; ++i)
1969 {
1970 data[1]++;
1971 Slice const s(data.data(), ss);
1972 keys.emplace_back(s);
1973 }
1974 return keys;
1975}
1976
1977STTx
1978createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
1979{
1980 auto fill = [&](auto& obj) {
1981 obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
1982 obj.setFieldU32(sfLedgerSequence, seq);
1983 obj.setFieldVL(sfUNLModifyValidator, txKey);
1984 };
1985 return STTx(ttUNL_MODIFY, fill);
1986}
1987
1988} // namespace xrpl::test
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
virtual Config & config()=0
std::unordered_set< uint256, beast::Uhash<> > features
Definition Config.h:261
Manager to create NegativeUNL votes.
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
static constexpr size_t kNegativeUnlHighWaterMark
An unreliable validator must have more than negativeUNLHighWaterMark validations in the last flag led...
static constexpr size_t kNegativeUnlMinLocalValsToVote
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.
static constexpr size_t kNewValidatorDisableSkip
We don't want to disable new validators immediately after adding them.
void purgeNewValidators(LedgerIndex seq)
Purge validators that are not new anymore.
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 kNegativeUnlLowWaterMark
A validator is considered unreliable if its validations is less than negativeUNLLowWaterMark in the l...
void doVoting(std::shared_ptr< Ledger const > const &prevLedger, hash_set< PublicKey > const &unlKeys, RCLValidations &validations, std::shared_ptr< SHAMap > const &initialSet)
Cast our local vote on the NegativeUNL candidates.
hash_map< NodeID, LedgerIndex > newValidators_
void 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:126
A public key.
Definition PublicKey.h:42
Wrapper over STValidation for generic Validation code.
Rules controlling protocol behavior.
Definition Rules.h:33
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:143
Application & app()
Definition Env.h:280
ManualTimeKeeper & timeKeeper()
Definition Env.h:293
beast::Journal const journal
Definition Env.h:184
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 make_shared(T... args)
Keylet const & negativeUNL() noexcept
The (fixed) index of the object containing the ledger negativeUNL.
Definition Indexes.cpp:228
FeatureBitset testableAmendments()
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 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.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
bool voteAndCheck(NetworkHistory &history, NodeID const &myId, std::size_t expect, PreVote const &pre=gDefaultPreVote)
Create a NegativeUNLVote object.
BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, xrpl)
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.
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2)
bool verifyPubKeyAndSeq(std::shared_ptr< Ledger const > const &l, hash_map< PublicKey, std::uint32_t > nUnlLedgerSeq)
Verify the content of negative UNL entries (public key and ledger sequence) of a ledger.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
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:139
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:259
CreateGenesisT const kCreateGenesis
@ tefFAILURE
Definition TER.h:156
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
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.
Validations< RCLValidationsAdaptor > RCLValidations
Alias for RCL-specific instantiation of generic Validations.
std::unordered_map< Key, Value, Hash, Pred, Allocator > hash_map
@ TapNone
Definition ApplyView.h:13
@ temDISABLED
Definition TER.h:100
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
BaseUInt< 256 > uint256
Definition base_uint.h:562
std::enable_if_t< std::is_same_v< T, char >||std::is_same_v< T, unsigned char >, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:215
BaseUInt< 160, detail::NodeIDTag > NodeID
NodeID is a 160-bit hash representing one node.
Definition UintTypes.h:39
T push_back(T... args)
T size(T... args)
Only reasonable parameters can be honored, e.g cannot hasToReEnable when nUNLSize == 0.
std::optional< int > numLedgers
if not specified, the number of ledgers in the history is calculated from negUNLSize,...
Utility class for creating validators and ledger history.
std::shared_ptr< 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
std::vector< std::shared_ptr< Ledger > > LedgerHistory
NetworkHistory(beast::unit_test::Suite &suite, Parameter const &p)
hash_set< PublicKey > unlKeySet
std::vector< NodeID > unlNodeIDs