rippled
Loading...
Searching...
No Matches
NegativeUNL_test.cpp
1//-----------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2020 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21
22#include <xrpld/app/consensus/RCLValidations.h>
23#include <xrpld/app/ledger/Ledger.h>
24#include <xrpld/app/misc/NegativeUNLVote.h>
25#include <xrpld/app/misc/ValidatorList.h>
26#include <xrpld/app/tx/apply.h>
27#include <xrpld/ledger/View.h>
28
29#include <xrpl/beast/unit_test.h>
30
31namespace ripple {
32namespace test {
33
34/*
35 * This file implements the following negative UNL related tests:
36 * -- test filling and applying ttUNL_MODIFY Tx and ledger update
37 * -- test ttUNL_MODIFY Tx failure without featureNegativeUNL amendment
38 * -- test the NegativeUNLVote class. The test cases are split to multiple
39 * test classes to allow parallel execution.
40 * -- test the negativeUNLFilter function
41 *
42 * Other negative UNL related tests such as ValidatorList and RPC related ones
43 * are put in their existing unit test files.
44 */
45
56bool
59 size_t size,
60 bool hasToDisable,
61 bool hasToReEnable);
62
72bool
73applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
74
84bool
87 hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq);
88
97
106
115STTx
116createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
117
119{
127 void
129 {
130 /*
131 * test cases:
132 *
133 * (1) the ledger after genesis
134 * -- cannot apply Disable Tx
135 * -- cannot apply ReEnable Tx
136 * -- nUNL empty
137 * -- no ToDisable
138 * -- no ToReEnable
139 *
140 * (2) a flag ledger
141 * -- apply an Disable Tx
142 * -- cannot apply the second Disable Tx
143 * -- cannot apply a ReEnable Tx
144 * -- nUNL empty
145 * -- has ToDisable with right nodeId
146 * -- no ToReEnable
147 * ++ extra test: first Disable Tx in ledger TxSet
148 *
149 * (3) ledgers before the next flag ledger
150 * -- nUNL empty
151 * -- has ToDisable with right nodeId
152 * -- no ToReEnable
153 *
154 * (4) next flag ledger
155 * -- nUNL size == 1, with right nodeId
156 * -- no ToDisable
157 * -- no ToReEnable
158 * -- cannot apply an Disable Tx with nodeId already in nUNL
159 * -- apply an Disable Tx with different nodeId
160 * -- cannot apply a ReEnable Tx with the same NodeId as Add
161 * -- cannot apply a ReEnable Tx with a NodeId not in nUNL
162 * -- apply a ReEnable Tx with a nodeId already in nUNL
163 * -- has ToDisable with right nodeId
164 * -- has ToReEnable with right nodeId
165 * -- nUNL size still 1, right nodeId
166 *
167 * (5) ledgers before the next flag ledger
168 * -- nUNL size == 1, right nodeId
169 * -- has ToDisable with right nodeId
170 * -- has ToReEnable with right nodeId
171 *
172 * (6) next flag ledger
173 * -- nUNL size == 1, different nodeId
174 * -- no ToDisable
175 * -- no ToReEnable
176 * -- apply an Disable Tx with different nodeId
177 * -- nUNL size still 1, right nodeId
178 * -- has ToDisable with right nodeId
179 * -- no ToReEnable
180 *
181 * (7) ledgers before the next flag ledger
182 * -- nUNL size still 1, right nodeId
183 * -- has ToDisable with right nodeId
184 * -- no ToReEnable
185 *
186 * (8) next flag ledger
187 * -- nUNL size == 2
188 * -- apply a ReEnable Tx
189 * -- cannot apply second ReEnable Tx, even with right nodeId
190 * -- cannot apply an Disable Tx with the same NodeId as Remove
191 * -- nUNL size == 2
192 * -- no ToDisable
193 * -- has ToReEnable with right nodeId
194 *
195 * (9) ledgers before the next flag ledger
196 * -- nUNL size == 2
197 * -- no ToDisable
198 * -- has ToReEnable with right nodeId
199 *
200 * (10) next flag ledger
201 * -- nUNL size == 1
202 * -- apply a ReEnable Tx
203 * -- nUNL size == 1
204 * -- no ToDisable
205 * -- has ToReEnable with right nodeId
206 *
207 * (11) ledgers before the next flag ledger
208 * -- nUNL size == 1
209 * -- no ToDisable
210 * -- has ToReEnable with right nodeId
211 *
212 * (12) next flag ledger
213 * -- nUNL size == 0
214 * -- no ToDisable
215 * -- no ToReEnable
216 *
217 * (13) ledgers before the next flag ledger
218 * -- nUNL size == 0
219 * -- no ToDisable
220 * -- no ToReEnable
221 *
222 * (14) next flag ledger
223 * -- nUNL size == 0
224 * -- no ToDisable
225 * -- no ToReEnable
226 */
227
228 testcase("Create UNLModify Tx and apply to ledgers");
229
230 jtx::Env env(*this, jtx::supported_amendments() | featureNegativeUNL);
232 // genesis ledger
233 auto l = std::make_shared<Ledger>(
235 env.app().config(),
237 env.app().getNodeFamily());
238 BEAST_EXPECT(l->rules().enabled(featureNegativeUNL));
239
240 // Record the public keys and ledger sequences of expected negative UNL
241 // validators when we build the ledger history
243
244 {
245 //(1) the ledger after genesis, not a flag ledger
246 l = std::make_shared<Ledger>(
247 *l, env.app().timeKeeper().closeTime());
248
249 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
250 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
251
252 OpenView accum(&*l);
253 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
254 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
255 accum.apply(*l);
256 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
257 }
258
259 {
260 //(2) a flag ledger
261 // generate more ledgers
262 for (auto i = 0; i < 256 - 2; ++i)
263 {
264 l = std::make_shared<Ledger>(
265 *l, env.app().timeKeeper().closeTime());
266 }
267 BEAST_EXPECT(l->isFlagLedger());
268 l->updateNegativeUNL();
269
270 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
271 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
272 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
273
274 // can apply 1 and only 1 ToDisable Tx,
275 // cannot apply ToReEnable Tx, since negative UNL is empty
276 OpenView accum(&*l);
277 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
278 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, false));
279 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
280 accum.apply(*l);
281 auto good_size = negUnlSizeTest(l, 0, true, false);
282 BEAST_EXPECT(good_size);
283 if (good_size)
284 {
285 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
286 //++ first ToDisable Tx in ledger's TxSet
287 uint256 txID = txDisable_0.getTransactionID();
288 BEAST_EXPECT(l->txExists(txID));
289 }
290 }
291
292 {
293 //(3) ledgers before the next flag ledger
294 for (auto i = 0; i < 256; ++i)
295 {
296 auto good_size = negUnlSizeTest(l, 0, true, false);
297 BEAST_EXPECT(good_size);
298 if (good_size)
299 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
300 l = std::make_shared<Ledger>(
301 *l, env.app().timeKeeper().closeTime());
302 }
303 BEAST_EXPECT(l->isFlagLedger());
304 l->updateNegativeUNL();
305
306 //(4) next flag ledger
307 // test if the ledger updated correctly
308 auto good_size = negUnlSizeTest(l, 1, false, false);
309 BEAST_EXPECT(good_size);
310 if (good_size)
311 {
312 BEAST_EXPECT(*(l->negativeUNL().begin()) == publicKeys[0]);
313 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
314 }
315
316 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
317 auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
318 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
319 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
320 auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
321
322 OpenView accum(&*l);
323 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
324 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, true));
325 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
326 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
327 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
328 accum.apply(*l);
329 good_size = negUnlSizeTest(l, 1, true, true);
330 BEAST_EXPECT(good_size);
331 if (good_size)
332 {
333 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
334 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
335 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
336 // test sfFirstLedgerSequence
337 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
338 }
339 }
340
341 {
342 //(5) ledgers before the next flag ledger
343 for (auto i = 0; i < 256; ++i)
344 {
345 auto good_size = negUnlSizeTest(l, 1, true, true);
346 BEAST_EXPECT(good_size);
347 if (good_size)
348 {
349 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
350 BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
351 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
352 }
353 l = std::make_shared<Ledger>(
354 *l, env.app().timeKeeper().closeTime());
355 }
356 BEAST_EXPECT(l->isFlagLedger());
357 l->updateNegativeUNL();
358
359 //(6) next flag ledger
360 // test if the ledger updated correctly
361 auto good_size = negUnlSizeTest(l, 1, false, false);
362 BEAST_EXPECT(good_size);
363 if (good_size)
364 {
365 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
366 }
367
368 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
369
370 OpenView accum(&*l);
371 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
372 accum.apply(*l);
373 good_size = negUnlSizeTest(l, 1, true, false);
374 BEAST_EXPECT(good_size);
375 if (good_size)
376 {
377 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
378 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
379 nUnlLedgerSeq.emplace(publicKeys[1], l->seq());
380 nUnlLedgerSeq.erase(publicKeys[0]);
381 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
382 }
383 }
384
385 {
386 //(7) ledgers before the next flag ledger
387 for (auto i = 0; i < 256; ++i)
388 {
389 auto good_size = negUnlSizeTest(l, 1, true, false);
390 BEAST_EXPECT(good_size);
391 if (good_size)
392 {
393 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
394 BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
395 }
396 l = std::make_shared<Ledger>(
397 *l, env.app().timeKeeper().closeTime());
398 }
399 BEAST_EXPECT(l->isFlagLedger());
400 l->updateNegativeUNL();
401
402 //(8) next flag ledger
403 // test if the ledger updated correctly
404 auto good_size = negUnlSizeTest(l, 2, false, false);
405 BEAST_EXPECT(good_size);
406 if (good_size)
407 {
408 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
409 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
410 nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
411 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
412 }
413
414 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
415 auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
416 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
417
418 OpenView accum(&*l);
419 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
420 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
421 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
422 accum.apply(*l);
423 good_size = negUnlSizeTest(l, 2, false, true);
424 BEAST_EXPECT(good_size);
425 if (good_size)
426 {
427 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
428 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
429 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
430 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
431 }
432 }
433
434 {
435 //(9) ledgers before the next flag ledger
436 for (auto i = 0; i < 256; ++i)
437 {
438 auto good_size = negUnlSizeTest(l, 2, false, true);
439 BEAST_EXPECT(good_size);
440 if (good_size)
441 {
442 BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
443 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
444 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
445 }
446 l = std::make_shared<Ledger>(
447 *l, env.app().timeKeeper().closeTime());
448 }
449 BEAST_EXPECT(l->isFlagLedger());
450 l->updateNegativeUNL();
451
452 //(10) next flag ledger
453 // test if the ledger updated correctly
454 auto good_size = negUnlSizeTest(l, 1, false, false);
455 BEAST_EXPECT(good_size);
456 if (good_size)
457 {
458 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
459 nUnlLedgerSeq.erase(publicKeys[0]);
460 BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
461 }
462
463 auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
464
465 OpenView accum(&*l);
466 BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, true));
467 accum.apply(*l);
468 good_size = negUnlSizeTest(l, 1, false, true);
469 BEAST_EXPECT(good_size);
470 if (good_size)
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 good_size = negUnlSizeTest(l, 1, false, true);
483 BEAST_EXPECT(good_size);
484 if (good_size)
485 {
486 BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
487 BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
488 }
489 l = std::make_shared<Ledger>(
490 *l, env.app().timeKeeper().closeTime());
491 }
492 BEAST_EXPECT(l->isFlagLedger());
493 l->updateNegativeUNL();
494
495 //(12) next flag ledger
496 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
497 }
498
499 {
500 //(13) ledgers before the next flag ledger
501 for (auto i = 0; i < 256; ++i)
502 {
503 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
504 l = std::make_shared<Ledger>(
505 *l, env.app().timeKeeper().closeTime());
506 }
507 BEAST_EXPECT(l->isFlagLedger());
508 l->updateNegativeUNL();
509
510 //(14) next flag ledger
511 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
512 }
513 }
514
515 void
516 run() override
517 {
519 }
520};
521
523{
524 void
526 {
527 testcase("No negative UNL amendment");
528
529 jtx::Env env(*this, jtx::supported_amendments() - featureNegativeUNL);
531 // genesis ledger
532 auto l = std::make_shared<Ledger>(
534 env.app().config(),
536 env.app().getNodeFamily());
537 BEAST_EXPECT(!l->rules().enabled(featureNegativeUNL));
538
539 // generate more ledgers
540 for (auto i = 0; i < 256 - 1; ++i)
541 {
542 l = std::make_shared<Ledger>(
543 *l, env.app().timeKeeper().closeTime());
544 }
545 BEAST_EXPECT(l->seq() == 256);
546 auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
547 OpenView accum(&*l);
548 BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
549 accum.apply(*l);
550 BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
551 }
552
553 void
554 run() override
555 {
557 }
558};
559
564{
572 {
573 std::uint32_t numNodes; // number of validators
574 std::uint32_t negUNLSize; // size of negative UNL in the last ledger
575 bool hasToDisable; // if has ToDisable in the last ledger
576 bool hasToReEnable; // if has ToReEnable in the last ledger
582 };
583
585 : env(suite, jtx::supported_amendments() | featureNegativeUNL)
586 , param(p)
587 , validations(env.app().getValidations())
588 {
589 createNodes();
590 if (!param.numLedgers)
591 param.numLedgers = 256 * (param.negUNLSize + 1);
593 }
594
595 void
597 {
598 assert(param.numNodes <= 256);
600 for (int i = 0; i < param.numNodes; ++i)
601 {
602 UNLKeySet.insert(UNLKeys[i]);
603 UNLNodeIDs.push_back(calcNodeID(UNLKeys[i]));
604 UNLNodeIDSet.insert(UNLNodeIDs.back());
605 }
606 }
607
612 bool
614 {
615 static uint256 fake_amemdment; // So we have different genesis ledgers
616 auto l = std::make_shared<Ledger>(
618 env.app().config(),
619 std::vector<uint256>{fake_amemdment++},
620 env.app().getNodeFamily());
622
623 // When putting validators into the negative UNL, we start with
624 // validator 0, then validator 1 ...
625 int nidx = 0;
626 while (l->seq() <= param.numLedgers)
627 {
628 l = std::make_shared<Ledger>(
629 *l, env.app().timeKeeper().closeTime());
631
632 if (l->isFlagLedger())
633 {
634 l->updateNegativeUNL();
635 OpenView accum(&*l);
636 if (l->negativeUNL().size() < param.negUNLSize)
637 {
638 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
639 if (!applyAndTestResult(env, accum, tx, true))
640 break;
641 ++nidx;
642 }
643 else if (l->negativeUNL().size() == param.negUNLSize)
644 {
646 {
647 auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
648 if (!applyAndTestResult(env, accum, tx, true))
649 break;
650 ++nidx;
651 }
653 {
654 auto tx = createTx(false, l->seq(), UNLKeys[0]);
655 if (!applyAndTestResult(env, accum, tx, true))
656 break;
657 }
658 }
659 accum.apply(*l);
660 }
661 l->updateSkipList();
662 }
663 return negUnlSizeTest(
665 }
666
675 {
676 static auto keyPair = randomKeyPair(KeyType::secp256k1);
677 return std::make_shared<STValidation>(
678 env.app().timeKeeper().now(),
679 keyPair.first,
680 keyPair.second,
681 v,
682 [&](STValidation& v) {
683 v.setFieldH256(sfLedgerHash, ledger->info().hash);
684 v.setFieldU32(sfLedgerSequence, ledger->seq());
685 v.setFlag(vfFullValidation);
686 });
687 };
688
696 template <class NeedValidation>
697 void
698 walkHistoryAndAddValidations(NeedValidation&& needVal)
699 {
700 std::uint32_t curr = 0;
701 std::size_t need = 256 + 1;
702 // only last 256 + 1 ledgers need validations
703 if (history.size() > need)
704 curr = history.size() - need;
705 for (; curr != history.size(); ++curr)
706 {
707 for (std::size_t i = 0; i < param.numNodes; ++i)
708 {
709 if (needVal(history[curr], i))
710 {
712 v.setTrusted();
714 }
715 }
716 }
717 }
718
721 {
722 return history.back();
723 }
724
734};
735
736auto defaultPreVote = [](NegativeUNLVote& vote) {};
748template <typename PreVote = decltype(defaultPreVote)>
749bool
751 NetworkHistory& history,
752 NodeID const& myId,
753 std::size_t expect,
754 PreVote const& pre = defaultPreVote)
755{
756 NegativeUNLVote vote(myId, history.env.journal);
757 pre(vote);
758 auto txSet = std::make_shared<SHAMap>(
760 vote.doVoting(
761 history.lastLedger(), history.UNLKeySet, history.validations, txSet);
762 return countTx(txSet) == expect;
763}
764
769{
770 void
772 {
773 testcase("Create UNLModify Tx");
774 jtx::Env env(*this);
775
776 NodeID myId(0xA0);
777 NegativeUNLVote vote(myId, env.journal);
778
779 // one add, one remove
780 auto txSet = std::make_shared<SHAMap>(
782 PublicKey toDisableKey(
784 PublicKey toReEnableKey(
786 LedgerIndex seq(1234);
787 BEAST_EXPECT(countTx(txSet) == 0);
788 vote.addTx(seq, toDisableKey, NegativeUNLVote::ToDisable, txSet);
789 BEAST_EXPECT(countTx(txSet) == 1);
790 vote.addTx(seq, toReEnableKey, NegativeUNLVote::ToReEnable, txSet);
791 BEAST_EXPECT(countTx(txSet) == 2);
792 // content of a tx is implicitly tested after applied to a ledger
793 // in later test cases
794 }
795
796 void
798 {
799 testcase("Pick One Candidate");
800 jtx::Env env(*this);
801
802 NodeID myId(0xA0);
803 NegativeUNLVote vote(myId, env.journal);
804
805 uint256 pad_0(0);
806 uint256 pad_f = ~pad_0;
807 NodeID n_1(1);
808 NodeID n_2(2);
809 NodeID n_3(3);
810 std::vector<NodeID> candidates({n_1});
811 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
812 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_1);
813 candidates.emplace_back(2);
814 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
815 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_2);
816 candidates.emplace_back(3);
817 BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
818 BEAST_EXPECT(vote.choose(pad_f, candidates) == n_3);
819 }
820
821 void
823 {
824 testcase("Build Score Table");
825 /*
826 * 1. no skip list
827 * 2. short skip list
828 * 3. local node not enough history
829 * 4. a node double validated some seq
830 * 5. local node had enough validations but on a wrong chain
831 * 6. a good case, long enough history and perfect scores
832 */
833 {
834 // 1. no skip list
835 NetworkHistory history = {*this, {10, 0, false, false, 1}};
836 BEAST_EXPECT(history.goodHistory);
837 if (history.goodHistory)
838 {
839 NegativeUNLVote vote(
840 history.UNLNodeIDs[3], history.env.journal);
841 BEAST_EXPECT(!vote.buildScoreTable(
842 history.lastLedger(),
843 history.UNLNodeIDSet,
844 history.validations));
845 }
846 }
847
848 {
849 // 2. short skip list
850 NetworkHistory history = {*this, {10, 0, false, false, 256 / 2}};
851 BEAST_EXPECT(history.goodHistory);
852 if (history.goodHistory)
853 {
854 NegativeUNLVote vote(
855 history.UNLNodeIDs[3], history.env.journal);
856 BEAST_EXPECT(!vote.buildScoreTable(
857 history.lastLedger(),
858 history.UNLNodeIDSet,
859 history.validations));
860 }
861 }
862
863 {
864 // 3. local node not enough history
865 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
866 BEAST_EXPECT(history.goodHistory);
867 if (history.goodHistory)
868 {
869 NodeID myId = history.UNLNodeIDs[3];
872 std::size_t idx) -> bool {
873 // skip half my validations.
874 return !(
875 history.UNLNodeIDs[idx] == myId &&
876 l->seq() % 2 == 0);
877 });
878 NegativeUNLVote vote(myId, history.env.journal);
879 BEAST_EXPECT(!vote.buildScoreTable(
880 history.lastLedger(),
881 history.UNLNodeIDSet,
882 history.validations));
883 }
884 }
885
886 {
887 // 4. a node double validated some seq
888 // 5. local node had enough validations but on a wrong chain
889 NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
890 // We need two chains for these tests
891 bool wrongChainSuccess = history.goodHistory;
892 BEAST_EXPECT(wrongChainSuccess);
894 std::move(history.history);
895 // Create a new chain and use it as the one that majority of nodes
896 // follow
897 history.createLedgerHistory();
898 BEAST_EXPECT(history.goodHistory);
899
900 if (history.goodHistory && wrongChainSuccess)
901 {
902 NodeID myId = history.UNLNodeIDs[3];
903 NodeID badNode = history.UNLNodeIDs[4];
906 std::size_t idx) -> bool {
907 // everyone but me
908 return !(history.UNLNodeIDs[idx] == myId);
909 });
910
911 // local node validate wrong chain
912 // a node double validates
913 for (auto& l : wrongChain)
914 {
915 RCLValidation v1(history.createSTVal(l, myId));
916 history.validations.add(myId, v1);
917 RCLValidation v2(history.createSTVal(l, badNode));
918 history.validations.add(badNode, v2);
919 }
920
921 NegativeUNLVote vote(myId, history.env.journal);
922
923 // local node still on wrong chain, can build a scoreTable,
924 // but all other nodes' scores are zero
925 auto scoreTable = vote.buildScoreTable(
926 wrongChain.back(),
927 history.UNLNodeIDSet,
928 history.validations);
929 BEAST_EXPECT(scoreTable);
930 if (scoreTable)
931 {
932 for (auto const& [n, score] : *scoreTable)
933 {
934 if (n == myId)
935 BEAST_EXPECT(score == 256);
936 else
937 BEAST_EXPECT(score == 0);
938 }
939 }
940
941 // if local node switched to right history, but cannot build
942 // scoreTable because not enough local validations
943 BEAST_EXPECT(!vote.buildScoreTable(
944 history.lastLedger(),
945 history.UNLNodeIDSet,
946 history.validations));
947 }
948 }
949
950 {
951 // 6. a good case
952 NetworkHistory history = {*this, {10, 0, false, false, 256 + 1}};
953 BEAST_EXPECT(history.goodHistory);
954 if (history.goodHistory)
955 {
958 std::size_t idx) -> bool { return true; });
959 NegativeUNLVote vote(
960 history.UNLNodeIDs[3], history.env.journal);
961 auto scoreTable = vote.buildScoreTable(
962 history.lastLedger(),
963 history.UNLNodeIDSet,
964 history.validations);
965 BEAST_EXPECT(scoreTable);
966 if (scoreTable)
967 {
968 for (auto const& [_, score] : *scoreTable)
969 {
970 (void)_;
971 BEAST_EXPECT(score == 256);
972 }
973 }
974 }
975 }
976 }
977
990 bool
992 NegativeUNLVote& vote,
993 hash_set<NodeID> const& unl,
994 hash_set<NodeID> const& negUnl,
995 hash_map<NodeID, std::uint32_t> const& scoreTable,
996 std::size_t numDisable,
997 std::size_t numReEnable)
998 {
999 auto [disableCandidates, reEnableCandidates] =
1000 vote.findAllCandidates(unl, negUnl, scoreTable);
1001 bool rightDisable = disableCandidates.size() == numDisable;
1002 bool rightReEnable = reEnableCandidates.size() == numReEnable;
1003 return rightDisable && rightReEnable;
1004 };
1005
1006 void
1008 {
1009 testcase("Find All Candidates");
1010 /*
1011 * -- unl size: 35
1012 * -- negUnl size: 3
1013 *
1014 * 0. all good scores
1015 * 1. all bad scores
1016 * 2. all between watermarks
1017 * 3. 2 good scorers in negUnl
1018 * 4. 2 bad scorers not in negUnl
1019 * 5. 2 in negUnl but not in unl, have a remove candidate from score
1020 * table
1021 * 6. 2 in negUnl but not in unl, no remove candidate from score table
1022 * 7. 2 new validators have good scores, already in negUnl
1023 * 8. 2 new validators have bad scores, not in negUnl
1024 * 9. expired the new validators have bad scores, not in negUnl
1025 */
1026 NetworkHistory history = {*this, {35, 0, false, false, 0}};
1027
1028 hash_set<NodeID> negUnl_012;
1029 for (std::uint32_t i = 0; i < 3; ++i)
1030 negUnl_012.insert(history.UNLNodeIDs[i]);
1031
1032 // build a good scoreTable to use, or copy and modify
1033 hash_map<NodeID, std::uint32_t> goodScoreTable;
1034 for (auto const& n : history.UNLNodeIDs)
1035 goodScoreTable[n] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
1036
1037 NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal);
1038
1039 {
1040 // all good scores
1041 BEAST_EXPECT(checkCandidateSizes(
1042 vote, history.UNLNodeIDSet, negUnl_012, goodScoreTable, 0, 3));
1043 }
1044 {
1045 // all bad scores
1047 for (auto& n : history.UNLNodeIDs)
1048 scoreTable[n] = NegativeUNLVote::negativeUNLLowWaterMark - 1;
1049 BEAST_EXPECT(checkCandidateSizes(
1050 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 35 - 3, 0));
1051 }
1052 {
1053 // all between watermarks
1055 for (auto& n : history.UNLNodeIDs)
1056 scoreTable[n] = NegativeUNLVote::negativeUNLLowWaterMark + 1;
1057 BEAST_EXPECT(checkCandidateSizes(
1058 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 0));
1059 }
1060
1061 {
1062 // 2 good scorers in negUnl
1063 auto scoreTable = goodScoreTable;
1064 scoreTable[*negUnl_012.begin()] =
1066 BEAST_EXPECT(checkCandidateSizes(
1067 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 2));
1068 }
1069
1070 {
1071 // 2 bad scorers not in negUnl
1072 auto scoreTable = goodScoreTable;
1073 scoreTable[history.UNLNodeIDs[11]] =
1075 scoreTable[history.UNLNodeIDs[12]] =
1077 BEAST_EXPECT(checkCandidateSizes(
1078 vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 2, 3));
1079 }
1080
1081 {
1082 // 2 in negUnl but not in unl, have a remove candidate from score
1083 // table
1084 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1085 UNL_temp.erase(history.UNLNodeIDs[0]);
1086 UNL_temp.erase(history.UNLNodeIDs[1]);
1087 BEAST_EXPECT(checkCandidateSizes(
1088 vote, UNL_temp, negUnl_012, goodScoreTable, 0, 3));
1089 }
1090
1091 {
1092 // 2 in negUnl but not in unl, no remove candidate from score table
1093 auto scoreTable = goodScoreTable;
1094 scoreTable.erase(history.UNLNodeIDs[0]);
1095 scoreTable.erase(history.UNLNodeIDs[1]);
1096 scoreTable[history.UNLNodeIDs[2]] =
1098 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1099 UNL_temp.erase(history.UNLNodeIDs[0]);
1100 UNL_temp.erase(history.UNLNodeIDs[1]);
1101 BEAST_EXPECT(checkCandidateSizes(
1102 vote, UNL_temp, negUnl_012, scoreTable, 0, 2));
1103 }
1104
1105 {
1106 // 2 new validators
1107 NodeID new_1(0xbead);
1108 NodeID new_2(0xbeef);
1109 hash_set<NodeID> nowTrusted = {new_1, new_2};
1110 hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
1111 UNL_temp.insert(new_1);
1112 UNL_temp.insert(new_2);
1113 vote.newValidators(256, nowTrusted);
1114 {
1115 // 2 new validators have good scores, already in negUnl
1116 auto scoreTable = goodScoreTable;
1117 scoreTable[new_1] =
1119 scoreTable[new_2] =
1121 hash_set<NodeID> negUnl_temp = negUnl_012;
1122 negUnl_temp.insert(new_1);
1123 negUnl_temp.insert(new_2);
1124 BEAST_EXPECT(checkCandidateSizes(
1125 vote, UNL_temp, negUnl_temp, scoreTable, 0, 3 + 2));
1126 }
1127 {
1128 // 2 new validators have bad scores, not in negUnl
1129 auto scoreTable = goodScoreTable;
1130 scoreTable[new_1] = 0;
1131 scoreTable[new_2] = 0;
1132 BEAST_EXPECT(checkCandidateSizes(
1133 vote, UNL_temp, negUnl_012, scoreTable, 0, 3));
1134 }
1135 {
1136 // expired the new validators have bad scores, not in negUnl
1137 vote.purgeNewValidators(
1139 auto scoreTable = goodScoreTable;
1140 scoreTable[new_1] = 0;
1141 scoreTable[new_2] = 0;
1142 BEAST_EXPECT(checkCandidateSizes(
1143 vote, UNL_temp, negUnl_012, scoreTable, 2, 3));
1144 }
1145 }
1146 }
1147
1148 void
1150 {
1151 testcase("Find All Candidates Combination");
1152 /*
1153 * == combination 1:
1154 * -- unl size: 34, 35, 80
1155 * -- nUnl size: 0, 50%, all
1156 * -- score pattern: all 0, all negativeUNLLowWaterMark & +1 & -1, all
1157 * negativeUNLHighWaterMark & +1 & -1, all 100%
1158 *
1159 * == combination 2:
1160 * -- unl size: 34, 35, 80
1161 * -- negativeUNL size: 0, all
1162 * -- nUnl size: one on, one off, one on, one off,
1163 * -- score pattern: 2*(negativeUNLLowWaterMark, +1, -1) &
1164 * 2*(negativeUNLHighWaterMark, +1, -1) & rest
1165 * negativeUNLMinLocalValsToVote
1166 */
1167
1168 jtx::Env env(*this);
1169
1170 NodeID myId(0xA0);
1171 NegativeUNLVote vote(myId, env.journal);
1172
1173 std::array<std::uint32_t, 3> unlSizes = {34, 35, 80};
1174 std::array<std::uint32_t, 3> nUnlPercent = {0, 50, 100};
1176 0,
1184
1185 //== combination 1:
1186 {
1187 auto fillScoreTable =
1188 [&](std::uint32_t unl_size,
1189 std::uint32_t nUnl_size,
1190 std::uint32_t score,
1191 hash_set<NodeID>& unl,
1192 hash_set<NodeID>& negUnl,
1193 hash_map<NodeID, std::uint32_t>& scoreTable) {
1194 std::vector<NodeID> nodeIDs;
1196 for (auto const& k : keys)
1197 {
1198 nodeIDs.emplace_back(calcNodeID(k));
1199 unl.emplace(nodeIDs.back());
1200 scoreTable[nodeIDs.back()] = score;
1201 }
1202 for (std::uint32_t i = 0; i < nUnl_size; ++i)
1203 negUnl.insert(nodeIDs[i]);
1204 };
1205
1206 for (auto us : unlSizes)
1207 {
1208 for (auto np : nUnlPercent)
1209 {
1210 for (auto score : scores)
1211 {
1212 hash_set<NodeID> unl;
1213 hash_set<NodeID> negUnl;
1215 fillScoreTable(
1216 us, us * np / 100, score, unl, negUnl, scoreTable);
1217 BEAST_EXPECT(unl.size() == us);
1218 BEAST_EXPECT(negUnl.size() == us * np / 100);
1219 BEAST_EXPECT(scoreTable.size() == us);
1220
1221 std::size_t toDisable_expect = 0;
1222 std::size_t toReEnable_expect = 0;
1223 if (np == 0)
1224 {
1225 if (score <
1227 {
1228 toDisable_expect = us;
1229 }
1230 }
1231 else if (np == 50)
1232 {
1233 if (score >
1235 {
1236 toReEnable_expect = us * np / 100;
1237 }
1238 }
1239 else
1240 {
1241 if (score >
1243 {
1244 toReEnable_expect = us;
1245 }
1246 }
1247 BEAST_EXPECT(checkCandidateSizes(
1248 vote,
1249 unl,
1250 negUnl,
1251 scoreTable,
1252 toDisable_expect,
1253 toReEnable_expect));
1254 }
1255 }
1256 }
1257
1258 //== combination 2:
1259 {
1260 auto fillScoreTable =
1261 [&](std::uint32_t unl_size,
1262 std::uint32_t nUnl_percent,
1263 hash_set<NodeID>& unl,
1264 hash_set<NodeID>& negUnl,
1265 hash_map<NodeID, std::uint32_t>& scoreTable) {
1266 std::vector<NodeID> nodeIDs;
1268 createPublicKeys(unl_size);
1269 for (auto const& k : keys)
1270 {
1271 nodeIDs.emplace_back(calcNodeID(k));
1272 unl.emplace(nodeIDs.back());
1273 }
1274
1275 std::uint32_t nIdx = 0;
1276 for (auto score : scores)
1277 {
1278 scoreTable[nodeIDs[nIdx++]] = score;
1279 scoreTable[nodeIDs[nIdx++]] = score;
1280 }
1281 for (; nIdx < unl_size;)
1282 {
1283 scoreTable[nodeIDs[nIdx++]] = scores.back();
1284 }
1285
1286 if (nUnl_percent == 100)
1287 {
1288 negUnl = unl;
1289 }
1290 else if (nUnl_percent == 50)
1291 {
1292 for (std::uint32_t i = 1; i < unl_size; i += 2)
1293 negUnl.insert(nodeIDs[i]);
1294 }
1295 };
1296
1297 for (auto us : unlSizes)
1298 {
1299 for (auto np : nUnlPercent)
1300 {
1301 hash_set<NodeID> unl;
1302 hash_set<NodeID> negUnl;
1304
1305 fillScoreTable(us, np, unl, negUnl, scoreTable);
1306 BEAST_EXPECT(unl.size() == us);
1307 BEAST_EXPECT(negUnl.size() == us * np / 100);
1308 BEAST_EXPECT(scoreTable.size() == us);
1309
1310 std::size_t toDisable_expect = 0;
1311 std::size_t toReEnable_expect = 0;
1312 if (np == 0)
1313 {
1314 toDisable_expect = 4;
1315 }
1316 else if (np == 50)
1317 {
1318 toReEnable_expect = negUnl.size() - 6;
1319 }
1320 else
1321 {
1322 toReEnable_expect = negUnl.size() - 12;
1323 }
1324 BEAST_EXPECT(checkCandidateSizes(
1325 vote,
1326 unl,
1327 negUnl,
1328 scoreTable,
1329 toDisable_expect,
1330 toReEnable_expect));
1331 }
1332 }
1333 }
1334 }
1335 }
1336
1337 void
1339 {
1340 testcase("New Validators");
1341 jtx::Env env(*this);
1342
1343 NodeID myId(0xA0);
1344 NegativeUNLVote vote(myId, env.journal);
1345
1346 // test cases:
1347 // newValidators_ of the NegativeUNLVote empty, add one
1348 // add a new one and one already added
1349 // add a new one and some already added
1350 // purge and see some are expired
1351
1352 NodeID n1(0xA1);
1353 NodeID n2(0xA2);
1354 NodeID n3(0xA3);
1355
1356 vote.newValidators(2, {n1});
1357 BEAST_EXPECT(vote.newValidators_.size() == 1);
1358 if (vote.newValidators_.size() == 1)
1359 {
1360 BEAST_EXPECT(vote.newValidators_.begin()->first == n1);
1361 BEAST_EXPECT(vote.newValidators_.begin()->second == 2);
1362 }
1363
1364 vote.newValidators(3, {n1, n2});
1365 BEAST_EXPECT(vote.newValidators_.size() == 2);
1366 if (vote.newValidators_.size() == 2)
1367 {
1368 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1369 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1370 }
1371
1372 vote.newValidators(
1374 BEAST_EXPECT(vote.newValidators_.size() == 3);
1375 if (vote.newValidators_.size() == 3)
1376 {
1377 BEAST_EXPECT(vote.newValidators_[n1] == 2);
1378 BEAST_EXPECT(vote.newValidators_[n2] == 3);
1379 BEAST_EXPECT(
1380 vote.newValidators_[n3] ==
1382 }
1383
1385 BEAST_EXPECT(vote.newValidators_.size() == 3);
1387 BEAST_EXPECT(vote.newValidators_.size() == 2);
1389 BEAST_EXPECT(vote.newValidators_.size() == 1);
1390 BEAST_EXPECT(vote.newValidators_.begin()->first == n3);
1391 BEAST_EXPECT(
1392 vote.newValidators_.begin()->second ==
1394 }
1395
1396 void
1397 run() override
1398 {
1399 testAddTx();
1405 }
1406};
1407
1414{
1415 void
1417 {
1418 testcase("Build Score Table Combination");
1419 /*
1420 * local node good history, correct scores:
1421 * == combination:
1422 * -- unl size: 10, 34, 35, 50
1423 * -- score pattern: all 0, all 50%, all 100%, two 0% two 50% rest 100%
1424 */
1425 std::array<std::uint32_t, 4> unlSizes = {10, 34, 35, 50};
1426 std::array<std::array<std::uint32_t, 3>, 4> scorePattern = {
1427 {{{0, 0, 0}}, {{50, 50, 50}}, {{100, 100, 100}}, {{0, 50, 100}}}};
1428
1429 for (auto unlSize : unlSizes)
1430 {
1431 for (std::uint32_t sp = 0; sp < 4; ++sp)
1432 {
1433 NetworkHistory history = {
1434 *this, {unlSize, 0, false, false, 256 + 2}};
1435 BEAST_EXPECT(history.goodHistory);
1436 if (history.goodHistory)
1437 {
1438 NodeID myId = history.UNLNodeIDs[3];
1440 [&](std::shared_ptr<Ledger const> const& l,
1441 std::size_t idx) -> bool {
1442 std::size_t k;
1443 if (idx < 2)
1444 k = 0;
1445 else if (idx < 4)
1446 k = 1;
1447 else
1448 k = 2;
1449
1450 bool add_50 =
1451 scorePattern[sp][k] == 50 && l->seq() % 2 == 0;
1452 bool add_100 = scorePattern[sp][k] == 100;
1453 bool add_me = history.UNLNodeIDs[idx] == myId;
1454 return add_50 || add_100 || add_me;
1455 });
1456
1457 NegativeUNLVote vote(myId, history.env.journal);
1458 auto scoreTable = vote.buildScoreTable(
1459 history.lastLedger(),
1460 history.UNLNodeIDSet,
1461 history.validations);
1462 BEAST_EXPECT(scoreTable);
1463 if (scoreTable)
1464 {
1465 std::uint32_t i = 0; // looping unl
1466 auto checkScores = [&](std::uint32_t score,
1467 std::uint32_t k) -> bool {
1468 if (history.UNLNodeIDs[i] == myId)
1469 return score == 256;
1470 if (scorePattern[sp][k] == 0)
1471 return score == 0;
1472 if (scorePattern[sp][k] == 50)
1473 return score == 256 / 2;
1474 if (scorePattern[sp][k] == 100)
1475 return score == 256;
1476 else
1477 return false;
1478 };
1479 for (; i < 2; ++i)
1480 {
1481 BEAST_EXPECT(checkScores(
1482 (*scoreTable)[history.UNLNodeIDs[i]], 0));
1483 }
1484 for (; i < 4; ++i)
1485 {
1486 BEAST_EXPECT(checkScores(
1487 (*scoreTable)[history.UNLNodeIDs[i]], 1));
1488 }
1489 for (; i < unlSize; ++i)
1490 {
1491 BEAST_EXPECT(checkScores(
1492 (*scoreTable)[history.UNLNodeIDs[i]], 2));
1493 }
1494 }
1495 }
1496 }
1497 }
1498 }
1499
1500 void
1501 run() override
1502 {
1504 }
1505};
1506
1507/*
1508 * Test the doVoting function of NegativeUNLVote.
1509 * The test cases are split to 5 classes for parallel execution.
1510 *
1511 * Voting tests: (use hasToDisable and hasToReEnable in some of the cases)
1512 *
1513 * == all good score, nUnl empty
1514 * -- txSet.size = 0
1515 * == all good score, nUnl not empty (use hasToDisable)
1516 * -- txSet.size = 1
1517 *
1518 * == 2 nodes offline, nUnl empty (use hasToReEnable)
1519 * -- txSet.size = 1
1520 * == 2 nodes offline, in nUnl
1521 * -- txSet.size = 0
1522 *
1523 * == 2 nodes offline, not in nUnl, but maxListed
1524 * -- txSet.size = 0
1525 *
1526 * == 2 nodes offline including me, not in nUnl
1527 * -- txSet.size = 0
1528 * == 2 nodes offline, not in negativeUNL, but I'm not a validator
1529 * -- txSet.size = 0
1530 * == 2 in nUnl, but not in unl, no other remove candidates
1531 * -- txSet.size = 1
1532 *
1533 * == 2 new validators have bad scores
1534 * -- txSet.size = 0
1535 * == 2 expired new validators have bad scores
1536 * -- txSet.size = 1
1537 */
1538
1540{
1541 void
1543 {
1544 testcase("Do Voting");
1545
1546 {
1547 //== all good score, negativeUNL empty
1548 //-- txSet.size = 0
1549 NetworkHistory history = {*this, {51, 0, false, false, {}}};
1550 BEAST_EXPECT(history.goodHistory);
1551 if (history.goodHistory)
1552 {
1554 [&](std::shared_ptr<Ledger const> const& l,
1555 std::size_t idx) -> bool { return true; });
1556 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1557 }
1558 }
1559
1560 {
1561 // all good score, negativeUNL not empty (use hasToDisable)
1562 //-- txSet.size = 1
1563 NetworkHistory history = {*this, {37, 0, true, false, {}}};
1564 BEAST_EXPECT(history.goodHistory);
1565 if (history.goodHistory)
1566 {
1568 [&](std::shared_ptr<Ledger const> const& l,
1569 std::size_t idx) -> bool { return true; });
1570 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 1));
1571 }
1572 }
1573 }
1574
1575 void
1576 run() override
1577 {
1578 testDoVoting();
1579 }
1580};
1581
1583{
1584 void
1586 {
1587 testcase("Do Voting");
1588
1589 {
1590 //== 2 nodes offline, negativeUNL empty (use hasToReEnable)
1591 //-- txSet.size = 1
1592 NetworkHistory history = {*this, {29, 1, false, true, {}}};
1593 BEAST_EXPECT(history.goodHistory);
1594 if (history.goodHistory)
1595 {
1597 [&](std::shared_ptr<Ledger const> const& l,
1598 std::size_t idx) -> bool {
1599 // skip node 0 and node 1
1600 return idx > 1;
1601 });
1602 BEAST_EXPECT(
1603 voteAndCheck(history, history.UNLNodeIDs.back(), 1));
1604 }
1605 }
1606
1607 {
1608 // 2 nodes offline, in negativeUNL
1609 //-- txSet.size = 0
1610 NetworkHistory history = {*this, {30, 1, true, false, {}}};
1611 BEAST_EXPECT(history.goodHistory);
1612 if (history.goodHistory)
1613 {
1614 NodeID n1 =
1615 calcNodeID(*history.lastLedger()->negativeUNL().begin());
1616 NodeID n2 =
1617 calcNodeID(*history.lastLedger()->validatorToDisable());
1619 [&](std::shared_ptr<Ledger const> const& l,
1620 std::size_t idx) -> bool {
1621 // skip node 0 and node 1
1622 return history.UNLNodeIDs[idx] != n1 &&
1623 history.UNLNodeIDs[idx] != n2;
1624 });
1625 BEAST_EXPECT(
1626 voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1627 }
1628 }
1629 }
1630
1631 void
1632 run() override
1633 {
1634 testDoVoting();
1635 }
1636};
1637
1639{
1640 void
1642 {
1643 testcase("Do Voting");
1644
1645 {
1646 // 2 nodes offline, not in negativeUNL, but maxListed
1647 //-- txSet.size = 0
1648 NetworkHistory history = {*this, {32, 8, true, true, {}}};
1649 BEAST_EXPECT(history.goodHistory);
1650 if (history.goodHistory)
1651 {
1653 [&](std::shared_ptr<Ledger const> const& l,
1654 std::size_t idx) -> bool {
1655 // skip node 0 ~ 10
1656 return idx > 10;
1657 });
1658 BEAST_EXPECT(
1659 voteAndCheck(history, history.UNLNodeIDs.back(), 0));
1660 }
1661 }
1662 }
1663
1664 void
1665 run() override
1666 {
1667 testDoVoting();
1668 }
1669};
1670
1672{
1673 void
1675 {
1676 testcase("Do Voting");
1677
1678 {
1679 //== 2 nodes offline including me, not in negativeUNL
1680 //-- txSet.size = 0
1681 NetworkHistory history = {*this, {35, 0, false, false, {}}};
1682 BEAST_EXPECT(history.goodHistory);
1683 if (history.goodHistory)
1684 {
1686 [&](std::shared_ptr<Ledger const> const& l,
1687 std::size_t idx) -> bool { return idx > 1; });
1688 BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
1689 }
1690 }
1691
1692 {
1693 // 2 nodes offline, not in negativeUNL, but I'm not a validator
1694 //-- txSet.size = 0
1695 NetworkHistory history = {*this, {40, 0, false, false, {}}};
1696 BEAST_EXPECT(history.goodHistory);
1697 if (history.goodHistory)
1698 {
1700 [&](std::shared_ptr<Ledger const> const& l,
1701 std::size_t idx) -> bool { return idx > 1; });
1702 BEAST_EXPECT(voteAndCheck(history, NodeID(0xdeadbeef), 0));
1703 }
1704 }
1705
1706 {
1707 //== 2 in negativeUNL, but not in unl, no other remove candidates
1708 //-- txSet.size = 1
1709 NetworkHistory history = {*this, {25, 2, false, false, {}}};
1710 BEAST_EXPECT(history.goodHistory);
1711 if (history.goodHistory)
1712 {
1714 [&](std::shared_ptr<Ledger const> const& l,
1715 std::size_t idx) -> bool { return idx > 1; });
1716 BEAST_EXPECT(voteAndCheck(
1717 history,
1718 history.UNLNodeIDs.back(),
1719 1,
1720 [&](NegativeUNLVote& vote) {
1721 history.UNLKeySet.erase(history.UNLKeys[0]);
1722 history.UNLKeySet.erase(history.UNLKeys[1]);
1723 }));
1724 }
1725 }
1726 }
1727
1728 void
1729 run() override
1730 {
1731 testDoVoting();
1732 }
1733};
1734
1736{
1737 void
1739 {
1740 testcase("Do Voting");
1741
1742 {
1743 //== 2 new validators have bad scores
1744 //-- txSet.size = 0
1745 NetworkHistory history = {*this, {15, 0, false, false, {}}};
1746 BEAST_EXPECT(history.goodHistory);
1747 if (history.goodHistory)
1748 {
1750 [&](std::shared_ptr<Ledger const> const& l,
1751 std::size_t idx) -> bool { return true; });
1752 BEAST_EXPECT(voteAndCheck(
1753 history,
1754 history.UNLNodeIDs[0],
1755 0,
1756 [&](NegativeUNLVote& vote) {
1757 auto extra_key_1 =
1758 randomKeyPair(KeyType::ed25519).first;
1759 auto extra_key_2 =
1760 randomKeyPair(KeyType::ed25519).first;
1761 history.UNLKeySet.insert(extra_key_1);
1762 history.UNLKeySet.insert(extra_key_2);
1763 hash_set<NodeID> nowTrusted;
1764 nowTrusted.insert(calcNodeID(extra_key_1));
1765 nowTrusted.insert(calcNodeID(extra_key_2));
1766 vote.newValidators(
1767 history.lastLedger()->seq(), nowTrusted);
1768 }));
1769 }
1770 }
1771
1772 {
1773 //== 2 expired new validators have bad scores
1774 //-- txSet.size = 1
1775 NetworkHistory history = {
1776 *this,
1777 {21,
1778 0,
1779 false,
1780 false,
1782 BEAST_EXPECT(history.goodHistory);
1783 if (history.goodHistory)
1784 {
1786 [&](std::shared_ptr<Ledger const> const& l,
1787 std::size_t idx) -> bool { return true; });
1788 BEAST_EXPECT(voteAndCheck(
1789 history,
1790 history.UNLNodeIDs[0],
1791 1,
1792 [&](NegativeUNLVote& vote) {
1793 auto extra_key_1 =
1794 randomKeyPair(KeyType::ed25519).first;
1795 auto extra_key_2 =
1796 randomKeyPair(KeyType::ed25519).first;
1797 history.UNLKeySet.insert(extra_key_1);
1798 history.UNLKeySet.insert(extra_key_2);
1799 hash_set<NodeID> nowTrusted;
1800 nowTrusted.insert(calcNodeID(extra_key_1));
1801 nowTrusted.insert(calcNodeID(extra_key_2));
1802 vote.newValidators(256, nowTrusted);
1803 }));
1804 }
1805 }
1806 }
1807
1808 void
1809 run() override
1810 {
1811 testDoVoting();
1812 }
1813};
1814
1816{
1817 void
1819 {
1820 testcase("Filter Validations");
1821 jtx::Env env(*this);
1822 auto l = std::make_shared<Ledger>(
1824 env.app().config(),
1826 env.app().getNodeFamily());
1827
1828 auto createSTVal = [&](std::pair<PublicKey, SecretKey> const& keys) {
1829 return std::make_shared<STValidation>(
1830 env.app().timeKeeper().now(),
1831 keys.first,
1832 keys.second,
1833 calcNodeID(keys.first),
1834 [&](STValidation& v) {
1835 v.setFieldH256(sfLedgerHash, l->info().hash);
1836 v.setFieldU32(sfLedgerSequence, l->seq());
1837 v.setFlag(vfFullValidation);
1838 });
1839 };
1840
1841 // create keys and validations
1842 std::uint32_t numNodes = 10;
1843 std::uint32_t negUnlSize = 3;
1845 hash_set<NodeID> activeValidators;
1846 hash_set<PublicKey> nUnlKeys;
1848 for (int i = 0; i < numNodes; ++i)
1849 {
1850 auto keyPair = randomKeyPair(KeyType::secp256k1);
1851 vals.emplace_back(createSTVal(keyPair));
1852 cfgKeys.push_back(toBase58(TokenType::NodePublic, keyPair.first));
1853 activeValidators.emplace(calcNodeID(keyPair.first));
1854 if (i < negUnlSize)
1855 {
1856 nUnlKeys.insert(keyPair.first);
1857 }
1858 }
1859
1860 // setup the ValidatorList
1861 auto& validators = env.app().validators();
1862 auto& local = *nUnlKeys.begin();
1863 std::vector<std::string> cfgPublishers;
1864 validators.load(local, cfgKeys, cfgPublishers);
1865 validators.updateTrusted(
1866 activeValidators,
1867 env.timeKeeper().now(),
1868 env.app().getOPs(),
1869 env.app().overlay(),
1870 env.app().getHashRouter());
1871 BEAST_EXPECT(validators.getTrustedMasterKeys().size() == numNodes);
1872 validators.setNegativeUNL(nUnlKeys);
1873 BEAST_EXPECT(validators.getNegativeUNL().size() == negUnlSize);
1874
1875 // test the filter
1876 BEAST_EXPECT(vals.size() == numNodes);
1877 vals = validators.negativeUNLFilter(std::move(vals));
1878 BEAST_EXPECT(vals.size() == numNodes - negUnlSize);
1879 }
1880
1881 void
1882 run() override
1883 {
1885 }
1886};
1887
1888BEAST_DEFINE_TESTSUITE(NegativeUNL, ledger, ripple);
1889BEAST_DEFINE_TESTSUITE(NegativeUNLNoAmendment, ledger, ripple);
1890
1891BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, ripple);
1892BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, ripple);
1893BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteGoodScore, consensus, ripple, 1);
1894BEAST_DEFINE_TESTSUITE(NegativeUNLVoteOffline, consensus, ripple);
1895BEAST_DEFINE_TESTSUITE(NegativeUNLVoteMaxListed, consensus, ripple);
1896BEAST_DEFINE_TESTSUITE_PRIO(
1897 NegativeUNLVoteRetiredValidator,
1898 consensus,
1899 ripple,
1900 1);
1901BEAST_DEFINE_TESTSUITE(NegativeUNLVoteNewValidator, consensus, ripple);
1902BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, ripple);
1903
1907bool
1910 size_t size,
1911 bool hasToDisable,
1912 bool hasToReEnable)
1913{
1914 bool sameSize = l->negativeUNL().size() == size;
1915 bool sameToDisable =
1916 (l->validatorToDisable() != std::nullopt) == hasToDisable;
1917 bool sameToReEnable =
1918 (l->validatorToReEnable() != std::nullopt) == hasToReEnable;
1919
1920 return sameSize && sameToDisable && sameToReEnable;
1921}
1922
1923bool
1924applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
1925{
1926 auto const res =
1927 apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
1928 if (pass)
1929 return res.ter == tesSUCCESS;
1930 else
1931 return res.ter == tefFAILURE || res.ter == temDISABLED;
1932}
1933
1934bool
1938{
1939 auto sle = l->read(keylet::negativeUNL());
1940 if (!sle)
1941 return false;
1942 if (!sle->isFieldPresent(sfDisabledValidators))
1943 return false;
1944
1945 auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
1946 if (nUnlData.size() != nUnlLedgerSeq.size())
1947 return false;
1948
1949 for (auto const& n : nUnlData)
1950 {
1951 if (!n.isFieldPresent(sfFirstLedgerSequence) ||
1952 !n.isFieldPresent(sfPublicKey))
1953 return false;
1954
1955 auto seq = n.getFieldU32(sfFirstLedgerSequence);
1956 auto d = n.getFieldVL(sfPublicKey);
1957 auto s = makeSlice(d);
1958 if (!publicKeyType(s))
1959 return false;
1960 PublicKey pk(s);
1961 auto it = nUnlLedgerSeq.find(pk);
1962 if (it == nUnlLedgerSeq.end())
1963 return false;
1964 if (it->second != seq)
1965 return false;
1966 nUnlLedgerSeq.erase(it);
1967 }
1968 return nUnlLedgerSeq.size() == 0;
1969}
1970
1973{
1974 std::size_t count = 0;
1975 for (auto i = txSet->begin(); i != txSet->end(); ++i)
1976 {
1977 ++count;
1978 }
1979 return count;
1980};
1981
1984{
1986 std::size_t ss = 33;
1987 std::vector<uint8_t> data(ss, 0);
1988 data[0] = 0xED;
1989 for (int i = 0; i < n; ++i)
1990 {
1991 data[1]++;
1992 Slice s(data.data(), ss);
1993 keys.emplace_back(s);
1994 }
1995 return keys;
1996}
1997
1998STTx
1999createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
2000{
2001 auto fill = [&](auto& obj) {
2002 obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
2003 obj.setFieldU32(sfLedgerSequence, seq);
2004 obj.setFieldVL(sfUNLModifyValidator, txKey);
2005 };
2006 return STTx(ttUNL_MODIFY, fill);
2007}
2008
2009} // namespace test
2010} // namespace ripple
T back(T... args)
T begin(T... args)
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
virtual Config & config()=0
virtual Overlay & overlay()=0
virtual TimeKeeper & timeKeeper()=0
virtual NetworkOPs & getOPs()=0
virtual ValidatorList & validators()=0
virtual Family & getNodeFamily()=0
virtual HashRouter & getHashRouter()=0
Manager to create NegativeUNL votes.
static constexpr size_t negativeUNLHighWaterMark
An unreliable validator must have more than negativeUNLHighWaterMark validations in the last flag led...
NodeID choose(uint256 const &randomPadData, std::vector< NodeID > const &candidates)
Pick one candidate from a vector of candidates.
std::optional< hash_map< NodeID, std::uint32_t > > buildScoreTable(std::shared_ptr< Ledger const > const &prevLedger, hash_set< NodeID > const &unl, RCLValidations &validations)
Build a reliability measurement score table of validators' validation messages in the last flag ledge...
void purgeNewValidators(LedgerIndex seq)
Purge validators that are not new anymore.
Candidates const findAllCandidates(hash_set< NodeID > const &unl, hash_set< NodeID > const &negUnl, hash_map< NodeID, std::uint32_t > const &scoreTable)
Process the score table and find all disabling and re-enabling candidates.
static constexpr size_t newValidatorDisableSkip
We don't want to disable new validators immediately after adding them.
static constexpr size_t negativeUNLLowWaterMark
A validator is considered unreliable if its validations is less than negativeUNLLowWaterMark in the l...
void doVoting(std::shared_ptr< Ledger const > const &prevLedger, hash_set< PublicKey > const &unlKeys, RCLValidations &validations, std::shared_ptr< SHAMap > const &initialSet)
Cast our local vote on the NegativeUNL candidates.
hash_map< NodeID, LedgerIndex > newValidators_
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
void addTx(LedgerIndex seq, PublicKey const &vp, NegativeUNLModify modify, std::shared_ptr< SHAMap > const &initialSet)
Add a ttUNL_MODIFY Tx to the transaction set.
static constexpr size_t negativeUNLMinLocalValsToVote
The minimum number of validations of the local node for it to participate in the voting.
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:57
void apply(TxsRawView &to) const
Apply changes.
Definition: OpenView.cpp:131
A public key.
Definition: PublicKey.h:62
Wrapper over STValidation for generic Validation code.
An immutable linear range of bytes.
Definition: Slice.h:46
time_point now() const override
Returns the current time, using the server's clock.
Definition: TimeKeeper.h:64
time_point closeTime() const
Returns the predicted close time, in network time.
Definition: TimeKeeper.h:76
ValStatus add(NodeID const &nodeID, Validation const &val)
Add a new validation.
Definition: Validations.h:624
time_point now() const override
Returns the current time.
Test the private member functions of NegativeUNLVote.
bool checkCandidateSizes(NegativeUNLVote &vote, hash_set< NodeID > const &unl, hash_set< NodeID > const &negUnl, hash_map< NodeID, std::uint32_t > const &scoreTable, std::size_t numDisable, std::size_t numReEnable)
Find all candidates and check if the number of candidates meets expectation.
Rest the build score table function of NegativeUNLVote.
void run() override
Runs the suite.
void testNegativeUNL()
Test filling and applying ttUNL_MODIFY Tx, as well as ledger update:
A transaction testing environment.
Definition: Env.h:121
Application & app()
Definition: Env.h:261
beast::Journal const journal
Definition: Env.h:162
ManualTimeKeeper & timeKeeper()
Definition: Env.h:273
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)
Keylet const & negativeUNL() noexcept
The (fixed) index of the object containing the ledger negativeUNL.
Definition: Indexes.cpp:223
FeatureBitset supported_amendments()
Definition: Env.h:74
std::size_t countTx(std::shared_ptr< SHAMap > const &txSet)
Count the number of Tx in a TxSet.
bool negUnlSizeTest(std::shared_ptr< Ledger const > const &l, size_t size, bool hasToDisable, bool hasToReEnable)
Test the size of the negative UNL in a ledger, also test if the ledger has ToDisalbe and/or ToReEnabl...
std::vector< PublicKey > createPublicKeys(std::size_t n)
Create fake public keys.
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
bool applyAndTestResult(jtx::Env &env, OpenView &view, STTx const &tx, bool pass)
Try to apply a ttUNL_MODIFY Tx, and test the apply result.
bool voteAndCheck(NetworkHistory &history, NodeID const &myId, std::size_t expect, PreVote const &pre=defaultPreVote)
Create a NegativeUNLVote object.
bool VerifyPubKeyAndSeq(std::shared_ptr< Ledger const > const &l, hash_map< PublicKey, std::uint32_t > nUnlLedgerSeq)
Verify the content of negative UNL entries (public key and ledger sequence) of a ledger.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
std::uint32_t LedgerIndex
A ledger index.
Definition: Protocol.h:130
base_uint< 160, detail::NodeIDTag > NodeID
NodeID is a 160-bit hash representing one node.
Definition: UintTypes.h:59
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
Definition: SecretKey.cpp:331
@ tefFAILURE
Definition: TER.h:166
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Definition: PublicKey.cpp:223
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:244
SecretKey randomSecretKey()
Create a secret key using secure random numbers.
Definition: SecretKey.cpp:299
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
Definition: PublicKey.cpp:319
@ tesSUCCESS
Definition: TER.h:244
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition: apply.cpp:110
create_genesis_t const create_genesis
Definition: Ledger.cpp:51
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
Definition: SecretKey.cpp:386
@ tapNONE
Definition: ApplyView.h:32
@ temDISABLED
Definition: TER.h:114
T push_back(T... args)
T size(T... args)
Only reasonable parameters can be honored, e.g cannot hasToReEnable when nUNLSize == 0.
std::optional< int > numLedgers
if not specified, the number of ledgers in the history is calculated from negUNLSize,...
Utility class for creating validators and ledger history.
std::shared_ptr< Ledger const > lastLedger() const
void walkHistoryAndAddValidations(NeedValidation &&needVal)
Walk the ledger history and create validation messages for the ledgers.
NetworkHistory(beast::unit_test::suite &suite, Parameter const &p)
std::vector< NodeID > UNLNodeIDs
std::shared_ptr< STValidation > createSTVal(std::shared_ptr< Ledger const > const &ledger, NodeID const &v)
Create a validation.
std::vector< PublicKey > UNLKeys
bool createLedgerHistory()
create ledger history and apply needed ttUNL_MODIFY tx at flag ledgers
hash_set< PublicKey > UNLKeySet
Set the sequence number on a JTx.
Definition: seq.h:34