xrpld
Loading...
Searching...
No Matches
tests/libxrpl/consensus/Consensus.cpp
1#include <xrpl/consensus/Consensus.h>
2
3#include <xrpl/basics/UnorderedContainers.h>
4#include <xrpl/basics/chrono.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/consensus/ConsensusParms.h>
7#include <xrpl/consensus/ConsensusTypes.h>
8#include <xrpl/consensus/DisputedTx.h>
9#include <xrpl/ledger/LedgerTiming.h>
10
11#include <csf/Peer.h>
12#include <csf/PeerGroup.h>
13#include <csf/Sim.h>
14#include <csf/SimTime.h>
15#include <csf/Tx.h>
16#include <csf/Validation.h>
17#include <csf/collectors.h>
18#include <csf/events.h>
19#include <csf/random.h>
20#include <csf/submitters.h>
21#include <gtest/gtest.h>
22#include <helpers/TestSink.h>
23
24#include <chrono>
25#include <cstddef>
26#include <cstdint>
27#include <memory>
28#include <sstream>
29#include <string>
30#include <vector>
31
32namespace xrpl::test {
33
34namespace {
35
36beast::Journal
37journal()
38{
39 return beast::Journal{TestSink::instance()};
40}
41
42bool
44 bool anyTransactions,
45 std::size_t prevProposers,
46 std::size_t proposersClosed,
47 std::size_t proposersValidated,
48 std::chrono::milliseconds prevRoundTime,
49 std::chrono::milliseconds timeSincePrevClose,
50 std::chrono::milliseconds openTime,
51 std::chrono::milliseconds idleInterval,
52 ConsensusParms const& parms,
53 std::unique_ptr<std::stringstream> const& clog = {})
54{
56 anyTransactions,
57 prevProposers,
58 proposersClosed,
59 proposersValidated,
60 prevRoundTime,
61 timeSincePrevClose,
62 openTime,
63 idleInterval,
64 parms,
65 journal(),
66 clog);
67}
68
71 std::size_t prevProposers,
72 std::size_t currentProposers,
73 std::size_t currentAgree,
74 std::size_t currentFinished,
75 std::chrono::milliseconds previousAgreeTime,
76 std::chrono::milliseconds currentAgreeTime,
77 bool stalled,
78 ConsensusParms const& parms,
79 bool proposing,
80 std::unique_ptr<std::stringstream> const& clog = {})
81{
83 prevProposers,
84 currentProposers,
85 currentAgree,
86 currentFinished,
87 previousAgreeTime,
88 currentAgreeTime,
89 stalled,
90 parms,
91 proposing,
92 journal(),
93 clog);
94}
95
96using CsfDisputedTx = DisputedTx<csf::Tx, csf::PeerID>;
97
98CsfDisputedTx
99makeDisputedTx(csf::Tx tx, bool ourVote, std::size_t numPeers)
100{
101 return CsfDisputedTx{tx, ourVote, numPeers, journal()};
102}
103
104bool
105isStalled(
106 CsfDisputedTx const& dispute,
107 ConsensusParms const& parms,
108 bool proposing,
109 int peersUnchanged,
110 std::unique_ptr<std::stringstream> const& clog)
111{
112 return dispute.stalled(parms, proposing, peersUnchanged, journal(), clog);
113}
114
115// Helper collector for testPreferredByBranch
116// Invasively disconnects network at bad times to cause splits
117struct Disruptor
118{
119 csf::PeerGroup& network;
120 csf::PeerGroup& groupCfast;
121 csf::PeerGroup& groupCsplit;
122 csf::SimDuration delay;
123 bool reconnected = false;
124
125 Disruptor(csf::PeerGroup& net, csf::PeerGroup& c, csf::PeerGroup& split, csf::SimDuration d)
126 : network(net), groupCfast(c), groupCsplit(split), delay(d)
127 {
128 }
129
130 template <class E>
131 void
132 on(csf::PeerID, csf::SimTime, E const&)
133 {
134 }
135
136 void
137 on(csf::PeerID who, csf::SimTime, csf::FullyValidateLedger const& e)
138 {
139 using namespace std::chrono;
140 // As soon as the fastC node fully validates C, disconnect
141 // ALL c nodes from the network. The fast C node needs to disconnect
142 // as well to prevent it from relaying the validations it did see
143 if (who == groupCfast[0]->id && e.ledger.seq() == csf::Ledger::Seq{2})
144 {
145 network.disconnect(groupCsplit);
146 network.disconnect(groupCfast);
147 }
148 }
149
150 void
151 on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e)
152 {
153 // As soon as anyone generates a child of B or C, reconnect the
154 // network so those validations make it through
155 if (!reconnected && e.ledger.seq() == csf::Ledger::Seq{3})
156 {
157 reconnected = true;
158 network.connect(groupCsplit, delay);
159 }
160 }
161};
162
163// Helper collector for testPauseForLaggards
164// This will remove the ledgerAccept delay used to
165// initially create the slow vs. fast validator groups.
166struct UndoDelay
167{
168 csf::PeerGroup& g;
169
170 UndoDelay(csf::PeerGroup& a) : g(a)
171 {
172 }
173
174 template <class E>
175 void
176 on(csf::PeerID, csf::SimTime, E const&)
177 {
178 }
179
180 void
181 on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e)
182 {
183 for (csf::Peer* p : g)
184 {
185 if (p->id == who)
186 p->delays.ledgerAccept = std::chrono::seconds{0};
187 }
188 }
189};
190
191} // namespace
192
193TEST(ConsensusTest, should_close_ledger)
194{
195 using namespace std::chrono_literals;
196 SCOPED_TRACE("should close ledger");
197
198 // Use default parameters
199 ConsensusParms const p{};
200
201 // Bizarre times forcibly close
202 EXPECT_TRUE(shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, p));
203 EXPECT_TRUE(shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, p));
204 EXPECT_TRUE(shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, p));
205
206 // Rest of network has closed
207 EXPECT_TRUE(shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, p));
208
209 // No transactions means wait until end of internval
210 EXPECT_TRUE(!shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, p));
211 EXPECT_TRUE(shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, p));
212
213 // Enforce minimum ledger open time
214 EXPECT_TRUE(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, p));
215
216 // Don't go too much faster than last time
217 EXPECT_TRUE(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, p));
218
219 EXPECT_TRUE(shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, p));
220}
221
222TEST(ConsensusTest, check_consensus)
223{
224 using namespace std::chrono_literals;
225 SCOPED_TRACE("check consensus");
226
227 // Use default parameters
228 ConsensusParms const p{};
229
231 // Disputes still in doubt
232 //
233 // Not enough time has elapsed
234 EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, false, p, true));
235
236 // If not enough peers have proposed, ensure
237 // more time for proposals
238 EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 4s, false, p, true));
239
240 // Enough time has elapsed and we all agree
241 EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 2, 0, 3s, 10s, false, p, true));
242
243 // Enough time has elapsed and we don't yet agree
244 EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 1, 0, 3s, 10s, false, p, true));
245
246 // Our peers have moved on
247 // Enough time has elapsed and we all agree
248 EXPECT_TRUE(ConsensusState::MovedOn == checkConsensus(10, 2, 1, 8, 3s, 10s, false, p, true));
249
250 // If no peers, don't agree until time has passed.
251 EXPECT_TRUE(ConsensusState::No == checkConsensus(0, 0, 0, 0, 3s, 10s, false, p, true));
252
253 // Agree if no peers and enough time has passed.
254 EXPECT_TRUE(ConsensusState::Yes == checkConsensus(0, 0, 0, 0, 3s, 16s, false, p, true));
255
256 // Expire if too much time has passed without agreement
257 EXPECT_TRUE(ConsensusState::Expired == checkConsensus(10, 8, 1, 0, 1s, 19s, false, p, true));
258
260 // Stalled
261 //
262 // Not enough time has elapsed
263 EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, true, p, true));
264
265 // If not enough peers have proposed, ensure
266 // more time for proposals
267 EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 4s, true, p, true));
268
269 // Enough time has elapsed and we all agree
270 EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 2, 0, 3s, 10s, true, p, true));
271
272 // Enough time has elapsed and we don't yet agree, but there's nothing
273 // left to dispute
274 EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 1, 0, 3s, 10s, true, p, true));
275
276 // Our peers have moved on
277 // Enough time has elapsed and we all agree, nothing left to dispute
278 EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 1, 8, 3s, 10s, true, p, true));
279
280 // If no peers, don't agree until time has passed.
281 EXPECT_TRUE(ConsensusState::No == checkConsensus(0, 0, 0, 0, 3s, 10s, true, p, true));
282
283 // Agree if no peers and enough time has passed.
284 EXPECT_TRUE(ConsensusState::Yes == checkConsensus(0, 0, 0, 0, 3s, 16s, true, p, true));
285
286 // We are done if there's nothing left to dispute, no matter how much
287 // time has passed
288 EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 8, 1, 0, 1s, 19s, true, p, true));
289}
290
291TEST(ConsensusTest, standalone)
292{
293 using namespace std::chrono_literals;
294 using namespace csf;
295 SCOPED_TRACE("standalone");
296
297 Sim s;
298 PeerGroup const peers = s.createGroup(1);
299 Peer* peer = peers[0];
300 peer->targetLedgers = 1;
301 peer->start();
302 peer->submit(Tx{1});
303
304 s.scheduler.step();
305
306 // Inspect that the proper ledger was created
307 auto const& lcl = peer->lastClosedLedger;
308 EXPECT_TRUE(peer->prevLedgerID() == lcl.id());
309 EXPECT_TRUE(lcl.seq() == Ledger::Seq{1});
310 EXPECT_TRUE(lcl.txs().size() == 1);
311 EXPECT_TRUE(lcl.txs().contains(Tx{1}));
312 EXPECT_TRUE(peer->prevProposers == 0);
313}
314
315TEST(ConsensusTest, peers_agree)
316{
317 using namespace csf;
318 using namespace std::chrono;
319 SCOPED_TRACE("peers agree");
320
321 ConsensusParms const parms{};
322 Sim sim;
323 PeerGroup peers = sim.createGroup(5);
324
325 // Connected trust and network graphs with single fixed delay
326 peers.trustAndConnect(peers, round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
327
328 // everyone submits their own ID as a TX
329 for (Peer* p : peers)
330 p->submit(Tx(static_cast<std::uint32_t>(p->id)));
331
332 sim.run(1);
333
334 // All peers are in sync
335 EXPECT_TRUE(sim.synchronized());
336 if (sim.synchronized())
337 {
338 for (Peer const* peer : peers)
339 {
340 auto const& lcl = peer->lastClosedLedger;
341 EXPECT_TRUE(lcl.id() == peer->prevLedgerID());
342 EXPECT_TRUE(lcl.seq() == Ledger::Seq{1});
343 // All peers proposed
344 EXPECT_TRUE(peer->prevProposers == peers.size() - 1);
345 // All transactions were accepted
346 for (std::uint32_t i = 0; i < peers.size(); ++i)
347 EXPECT_TRUE(lcl.txs().contains(Tx{i}));
348 }
349 }
350}
351
352TEST(ConsensusTest, slow_peers)
353{
354 using namespace csf;
355 using namespace std::chrono;
356 SCOPED_TRACE("slow peers");
357
358 // Several tests of a complete trust graph with a subset of peers
359 // that have significantly longer network delays to the rest of the
360 // network
361
362 // Test when a slow peer doesn't delay a consensus quorum (4/5 agree)
363 {
364 ConsensusParms const parms{};
365 Sim sim;
366 PeerGroup slow = sim.createGroup(1);
367 PeerGroup fast = sim.createGroup(4);
368 PeerGroup network = fast + slow;
369
370 // Fully connected trust graph
371 network.trust(network);
372
373 // Fast and slow network connections
374 fast.connect(fast, round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
375
376 slow.connect(network, round<milliseconds>(1.1 * parms.ledgerGRANULARITY));
377
378 // All peers submit their own ID as a transaction
379 for (Peer* peer : network)
380 peer->submit(Tx{static_cast<std::uint32_t>(peer->id)});
381
382 sim.run(1);
383
384 // Verify all peers have same LCL but are missing transaction 0
385 // All peers are in sync even with a slower peer 0
386 EXPECT_TRUE(sim.synchronized());
387 if (sim.synchronized())
388 {
389 for (Peer const* peer : network)
390 {
391 auto const& lcl = peer->lastClosedLedger;
392 EXPECT_TRUE(lcl.id() == peer->prevLedgerID());
393 EXPECT_TRUE(lcl.seq() == Ledger::Seq{1});
394
395 EXPECT_TRUE(peer->prevProposers == network.size() - 1);
396 EXPECT_TRUE(peer->prevRoundTime == network[0]->prevRoundTime);
397
398 EXPECT_TRUE(not lcl.txs().contains(Tx{0}));
399 for (std::uint32_t i = 2; i < network.size(); ++i)
400 EXPECT_TRUE(lcl.txs().contains(Tx{i}));
401
402 // Tx 0 didn't make it
403 EXPECT_TRUE(peer->openTxs.contains(Tx{0}));
404 }
405 }
406 }
407
408 // Test when the slow peers delay a consensus quorum (4/6 agree)
409 {
410 // Run two tests
411 // 1. The slow peers are participating in consensus
412 // 2. The slow peers are just observing
413
414 for (auto isParticipant : {true, false})
415 {
416 ConsensusParms const parms{};
417
418 Sim sim;
419 PeerGroup slow = sim.createGroup(2);
420 PeerGroup fast = sim.createGroup(4);
421 PeerGroup network = fast + slow;
422
423 // Connected trust graph
424 network.trust(network);
425
426 // Fast and slow network connections
427 fast.connect(fast, round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
428
429 slow.connect(network, round<milliseconds>(1.1 * parms.ledgerGRANULARITY));
430
431 for (Peer* peer : slow)
432 peer->runAsValidator = isParticipant;
433
434 // All peers submit their own ID as a transaction and relay it
435 // to peers
436 for (Peer* peer : network)
437 peer->submit(Tx{static_cast<std::uint32_t>(peer->id)});
438
439 sim.run(1);
440
441 EXPECT_TRUE(sim.synchronized());
442 if (sim.synchronized())
443 {
444 // Verify all peers have same LCL but are missing
445 // transaction 0,1 which was not received by all peers
446 // before the ledger closed
447 for (Peer const* peer : network)
448 {
449 // Closed ledger has all but transaction 0,1
450 auto const& lcl = peer->lastClosedLedger;
451 EXPECT_TRUE(lcl.seq() == Ledger::Seq{1});
452 EXPECT_TRUE(not lcl.txs().contains(Tx{0}));
453 EXPECT_TRUE(not lcl.txs().contains(Tx{1}));
454 for (std::uint32_t i = slow.size(); i < network.size(); ++i)
455 EXPECT_TRUE(lcl.txs().contains(Tx{i}));
456
457 // Tx 0-1 didn't make it
458 EXPECT_TRUE(peer->openTxs.contains(Tx{0}));
459 EXPECT_TRUE(peer->openTxs.contains(Tx{1}));
460 }
461
462 Peer const* slowPeer = slow[0];
463 if (isParticipant)
464 {
465 EXPECT_TRUE(slowPeer->prevProposers == network.size() - 1);
466 }
467 else
468 {
469 EXPECT_TRUE(slowPeer->prevProposers == fast.size());
470 }
471
472 for (Peer const* peer : fast)
473 {
474 // Due to the network link delay settings
475 // Peer 0 initially proposes {0}
476 // Peer 1 initially proposes {1}
477 // Peers 2-5 initially propose {2,3,4,5}
478 // Since peers 2-5 agree, 4/6 > the initial 50% needed
479 // to include a disputed transaction, so Peer 0/1 switch
480 // to agree with those peers. Peer 0/1 then closes with
481 // an 80% quorum of agreeing positions (5/6) match.
482 //
483 // Peers 2-5 do not change position, since tx 0 or tx 1
484 // have less than the 50% initial threshold. They also
485 // cannot declare consensus, since 4/6 agreeing
486 // positions are < 80% threshold. They therefore need an
487 // additional timerEntry call to see the updated
488 // positions from Peer 0 & 1.
489
490 if (isParticipant)
491 {
492 EXPECT_TRUE(peer->prevProposers == network.size() - 1);
493 EXPECT_TRUE(peer->prevRoundTime > slowPeer->prevRoundTime);
494 }
495 else
496 {
497 EXPECT_TRUE(peer->prevProposers == fast.size() - 1);
498 // so all peers should have closed together
499 EXPECT_TRUE(peer->prevRoundTime == slowPeer->prevRoundTime);
500 }
501 }
502 }
503 }
504 }
505}
506
507TEST(ConsensusTest, close_time_disagree)
508{
509 using namespace csf;
510 using namespace std::chrono;
511 SCOPED_TRACE("close time disagree");
512
513 // This is a very specialized test to get ledgers to disagree on
514 // the close time. It unfortunately assumes knowledge about current
515 // timing constants. This is a necessary evil to get coverage up
516 // pending more extensive refactorings of timing constants.
517
518 // In order to agree-to-disagree on the close time, there must be no
519 // clear majority of nodes agreeing on a close time. This test
520 // sets a relative offset to the peers internal clocks so that they
521 // send proposals with differing times.
522
523 // However, agreement is on the effective close time, not the
524 // exact close time. The minimum closeTimeResolution is given by
525 // ledgerPossibleTimeResolutions[0], which is currently 10s. This means
526 // the skews need to be at least 10 seconds to have different effective
527 // close times.
528
529 // Complicating this matter is that nodes will ignore proposals
530 // with times more than proposeFRESHNESS =20s in the past. So at
531 // the minimum granularity, we have at most 3 types of skews
532 // (0s,10s,20s).
533
534 // This test therefore has 6 nodes, with 2 nodes having each type of
535 // skew. Then no majority (1/3 < 1/2) of nodes will agree on an
536 // actual close time.
537
538 ConsensusParms const parms{};
539 Sim sim;
540
541 PeerGroup groupA = sim.createGroup(2);
542 PeerGroup const groupB = sim.createGroup(2);
543 PeerGroup const groupC = sim.createGroup(2);
544 PeerGroup network = groupA + groupB + groupC;
545
546 network.trust(network);
547 network.connect(network, round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
548
549 // Run consensus without skew until we have a short close time
550 // resolution
551 Peer const* firstPeer = *groupA.begin();
552 while (firstPeer->lastClosedLedger.closeTimeResolution() >= parms.proposeFRESHNESS)
553 sim.run(1);
554
555 // Introduce a shift on the time of 2/3 of peers
556 for (Peer* peer : groupA)
557 peer->clockSkew = parms.proposeFRESHNESS / 2;
558 for (Peer* peer : groupB)
559 peer->clockSkew = parms.proposeFRESHNESS;
560
561 sim.run(1);
562
563 // All nodes agreed to disagree on the close time
564 EXPECT_TRUE(sim.synchronized());
565 if (sim.synchronized())
566 {
567 for (Peer const* peer : network)
568 EXPECT_TRUE(!peer->lastClosedLedger.closeAgree());
569 }
570}
571
572TEST(ConsensusTest, wrong_lcl)
573{
574 using namespace csf;
575 using namespace std::chrono;
576 SCOPED_TRACE("wrong LCL");
577
578 // Specialized test to exercise a temporary fork in which some peers
579 // are working on an incorrect prior ledger.
580
581 ConsensusParms const parms{};
582
583 // Vary the time it takes to process validations to exercise detecting
584 // the wrong LCL at different phases of consensus
585 for (auto validationDelay : {0ms, parms.ledgerMinClose})
586 {
587 // Consider 10 peers:
588 // 0 1 2 3 4 5 6 7 8 9
589 // minority majorityA majorityB
590 //
591 // Nodes 0-1 trust nodes 0-4
592 // Nodes 2-9 trust nodes 2-9
593 //
594 // By submitting tx 0 to nodes 0-4 and tx 1 to nodes 5-9,
595 // nodes 0-1 will generate the wrong LCL (with tx 0). The remaining
596 // nodes will instead accept the ledger with tx 1.
597
598 // Nodes 0-1 will detect this mismatch during a subsequent round
599 // since nodes 2-4 will validate a different ledger.
600
601 // Nodes 0-1 will acquire the proper ledger from the network and
602 // resume consensus and eventually generate the dominant network
603 // ledger.
604
605 // This topology can potentially fork with the above trust relations
606 // but that is intended for this test.
607
608 Sim sim;
609
610 PeerGroup minority = sim.createGroup(2);
611 PeerGroup const majorityA = sim.createGroup(3);
612 PeerGroup const majorityB = sim.createGroup(5);
613
614 PeerGroup majority = majorityA + majorityB;
615 PeerGroup const network = minority + majority;
616
617 SimDuration const delay = round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
618 minority.trustAndConnect(minority + majorityA, delay);
619 majority.trustAndConnect(majority, delay);
620
621 CollectByNode<JumpCollector> jumps;
622 sim.collectors.add(jumps);
623
624 EXPECT_TRUE(sim.trustGraph.canFork(parms.minConsensusPct / 100.));
625
626 // initial round to set prior state
627 sim.run(1);
628
629 // Nodes in smaller UNL have seen tx 0, nodes in other unl have seen
630 // tx 1
631 for (Peer* peer : network)
632 peer->delays.recvValidation = validationDelay;
633 for (Peer* peer : (minority + majorityA))
634 peer->openTxs.insert(Tx{0});
635 for (Peer* peer : majorityB)
636 peer->openTxs.insert(Tx{1});
637
638 // Run for additional rounds
639 // With no validation delay, only 2 more rounds are needed.
640 // 1. Round to generate different ledgers
641 // 2. Round to detect different prior ledgers (but still generate
642 // wrong ones) and recover within that round since wrong LCL
643 // is detected before we close
644 //
645 // With a validation delay of ledgerMinClose, we need 3 more
646 // rounds.
647 // 1. Round to generate different ledgers
648 // 2. Round to detect different prior ledgers (but still generate
649 // wrong ones) but end up declaring consensus on wrong LCL (but
650 // with the right transaction set!). This is because we detect
651 // the wrong LCL after we have closed the ledger, so we declare
652 // consensus based solely on our peer proposals. But we haven't
653 // had time to acquire the right ledger.
654 // 3. Round to correct
655 sim.run(3);
656
657 // The network never actually forks, since node 0-1 never see a
658 // quorum of validations to fully validate the incorrect chain.
659
660 // However, for a non zero-validation delay, the network is not
661 // synchronized because nodes 0 and 1 are running one ledger behind
662 EXPECT_TRUE(sim.branches() == 1);
663 if (sim.branches() == 1)
664 {
665 for (Peer const* peer : majority)
666 {
667 // No jumps for majority nodes
668 EXPECT_TRUE(jumps[peer->id].closeJumps.empty());
669 EXPECT_TRUE(jumps[peer->id].fullyValidatedJumps.empty());
670 }
671 for (Peer const* peer : minority)
672 {
673 auto& peerJumps = jumps[peer->id];
674 // last closed ledger jump between chains
675 {
676 EXPECT_TRUE(peerJumps.closeJumps.size() == 1);
677 if (peerJumps.closeJumps.size() == 1)
678 {
679 JumpCollector::Jump const& jump = peerJumps.closeJumps.front();
680 // Jump is to a different chain
681 EXPECT_TRUE(jump.from.seq() <= jump.to.seq());
682 EXPECT_TRUE(!jump.to.isAncestor(jump.from));
683 }
684 }
685 // fully validated jump forward in same chain
686 {
687 EXPECT_TRUE(peerJumps.fullyValidatedJumps.size() == 1);
688 if (peerJumps.fullyValidatedJumps.size() == 1)
689 {
690 JumpCollector::Jump const& jump = peerJumps.fullyValidatedJumps.front();
691 // Jump is to a different chain with same seq
692 EXPECT_TRUE(jump.from.seq() < jump.to.seq());
693 EXPECT_TRUE(jump.to.isAncestor(jump.from));
694 }
695 }
696 }
697 }
698 }
699
700 {
701 // Additional test engineered to switch LCL during the establish
702 // phase. This was added to trigger a scenario that previously
703 // crashed, in which switchLCL switched from establish to open
704 // phase, but still processed the establish phase logic.
705
706 // Loner node will accept an initial ledger A, but all other nodes
707 // accept ledger B a bit later. By delaying the time it takes
708 // to process a validation, loner node will detect the wrongLCL
709 // after it is already in the establish phase of the next round.
710
711 Sim sim;
712 PeerGroup loner = sim.createGroup(1);
713 PeerGroup const friends = sim.createGroup(3);
714 loner.trust(loner + friends);
715
716 PeerGroup const others = sim.createGroup(6);
717 PeerGroup clique = friends + others;
718 clique.trust(clique);
719
720 PeerGroup network = loner + clique;
721 network.connect(network, round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
722
723 // initial round to set prior state
724 sim.run(1);
725 for (Peer* peer : (loner + friends))
726 peer->openTxs.insert(Tx(0));
727 for (Peer* peer : others)
728 peer->openTxs.insert(Tx(1));
729
730 // Delay validation processing
731 for (Peer* peer : network)
732 peer->delays.recvValidation = parms.ledgerGRANULARITY;
733
734 // additional rounds to generate wrongLCL and recover
735 sim.run(2);
736
737 // Check all peers recovered
738 for (Peer const* p : network)
739 EXPECT_TRUE(p->prevLedgerID() == network[0]->prevLedgerID());
740 }
741}
742
743TEST(ConsensusTest, consensus_close_time_rounding)
744{
745 using namespace csf;
746 using namespace std::chrono;
747 SCOPED_TRACE("consensus close time rounding");
748
749 // This is a specialized test engineered to yield ledgers with different
750 // close times even though the peers believe they had close time
751 // consensus on the ledger.
752 ConsensusParms const parms;
753
754 Sim sim;
755
756 // This requires a group of 4 fast and 2 slow peers to create a
757 // situation in which a subset of peers requires seeing additional
758 // proposals to declare consensus.
759 PeerGroup slow = sim.createGroup(2);
760 PeerGroup fast = sim.createGroup(4);
761 PeerGroup network = fast + slow;
762
763 // Connected trust graph
764 network.trust(network);
765
766 // Fast and slow network connections
767 fast.connect(fast, round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
768 slow.connect(network, round<milliseconds>(1.1 * parms.ledgerGRANULARITY));
769
770 // Run to the ledger *prior* to decreasing the resolution
772
773 // In order to create the discrepancy, we want a case where if
774 // X = effCloseTime(closeTime, resolution, parentCloseTime)
775 // X != effCloseTime(X, resolution, parentCloseTime)
776 //
777 // That is, the effective close time is not a fixed point. This can
778 // happen if X = parentCloseTime + 1, but a subsequent rounding goes
779 // to the next highest multiple of resolution.
780
781 // So we want to find an offset (now + offset) % 30s = 15
782 // (now + offset) % 20s = 15
783 // This way, the next ledger will close and round up Due to the
784 // network delay settings, the round of consensus will take 5s, so
785 // the next ledger's close time will
786
787 NetClock::duration when = network[0]->now().time_since_epoch();
788
789 // Check we are before the 30s to 20s transition
790 NetClock::duration const resolution = network[0]->lastClosedLedger.closeTimeResolution();
791 EXPECT_TRUE(resolution == NetClock::duration{30s});
792
793 while (((when % NetClock::duration{30s}) != NetClock::duration{15s}) ||
794 ((when % NetClock::duration{20s}) != NetClock::duration{15s}))
795 when += 1s;
796 // Advance the clock without consensus running (IS THIS WHAT
797 // PREVENTS IT IN PRACTICE?)
798 sim.scheduler.stepFor(NetClock::time_point{when} - network[0]->now());
799
800 // Run one more ledger with 30s resolution
801 sim.run(1);
802 EXPECT_TRUE(sim.synchronized());
803 if (sim.synchronized())
804 {
805 // close time should be ahead of clock time since we engineered
806 // the close time to round up
807 for (Peer const* peer : network)
808 {
809 EXPECT_TRUE(peer->lastClosedLedger.closeTime() > peer->now());
810 EXPECT_TRUE(peer->lastClosedLedger.closeAgree());
811 }
812 }
813
814 // All peers submit their own ID as a transaction
815 for (Peer* peer : network)
816 peer->submit(Tx{static_cast<std::uint32_t>(peer->id)});
817
818 // Run 1 more round, this time it will have a decreased
819 // resolution of 20 seconds.
820
821 // The network delays are engineered so that the slow peers
822 // initially have the wrong tx hash, but they see a majority
823 // of agreement from their peers and declare consensus
824 //
825 // The trick is that everyone starts with a raw close time of
826 // 84681s
827 // Which has
828 // effCloseTime(86481s, 20s, 86490s) = 86491s
829 // However, when the slow peers update their position, they change
830 // the close time to 86451s. The fast peers declare consensus with
831 // the 86481s as their position still.
832 //
833 // When accepted the ledger
834 // - fast peers use eff(86481s) -> 86491s as the close time
835 // - slow peers use eff(eff(86481s)) -> eff(86491s) -> 86500s!
836
837 sim.run(1);
838
839 EXPECT_TRUE(sim.synchronized());
840}
841
842TEST(ConsensusTest, fork)
843{
844 using namespace csf;
845 using namespace std::chrono;
846 SCOPED_TRACE("fork");
847
848 std::uint32_t const numPeers = 10;
849 // Vary overlap between two UNLs
850 for (std::uint32_t overlap = 0; overlap <= numPeers; ++overlap)
851 {
852 ConsensusParms const parms{};
853 Sim sim;
854
855 std::uint32_t const numA = (numPeers - overlap) / 2;
856 std::uint32_t const numB = numPeers - numA - overlap;
857
858 PeerGroup const aOnly = sim.createGroup(numA);
859 PeerGroup const bOnly = sim.createGroup(numB);
860 PeerGroup const commonOnly = sim.createGroup(overlap);
861
862 PeerGroup a = aOnly + commonOnly;
863 PeerGroup b = bOnly + commonOnly;
864
865 PeerGroup const network = a + b;
866
867 SimDuration const delay = round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
868 a.trustAndConnect(a, delay);
869 b.trustAndConnect(b, delay);
870
871 // Initial round to set prior state
872 sim.run(1);
873 for (Peer* peer : network)
874 {
875 // Nodes have only seen transactions from their neighbors
876 peer->openTxs.insert(Tx{static_cast<std::uint32_t>(peer->id)});
877 for (Peer const* to : sim.trustGraph.trustedPeers(peer))
878 peer->openTxs.insert(Tx{static_cast<std::uint32_t>(to->id)});
879 }
880 sim.run(1);
881
882 // Fork should not happen for 40% or greater overlap
883 // Since the overlapped nodes have a UNL that is the union of the
884 // two cliques, the maximum sized UNL list is the number of peers
885 if (overlap > 0.4 * numPeers)
886 {
887 EXPECT_TRUE(sim.synchronized());
888 }
889 else
890 {
891 // Even if we do fork, there shouldn't be more than 3 ledgers
892 // One for cliqueA, one for cliqueB and one for nodes in both
893 EXPECT_TRUE(sim.branches() <= 3);
894 }
895 }
896}
897
898TEST(ConsensusTest, hub_network)
899{
900 using namespace csf;
901 using namespace std::chrono;
902 SCOPED_TRACE("hub network");
903
904 // Simulate a set of 5 validators that aren't directly connected but
905 // rely on a single hub node for communication
906
907 ConsensusParms const parms{};
908 Sim sim;
909 PeerGroup validators = sim.createGroup(5);
910 PeerGroup center = sim.createGroup(1);
911 validators.trust(validators);
912 center.trust(validators);
913
914 SimDuration const delay = round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
915 validators.connect(center, delay);
916
917 center[0]->runAsValidator = false;
918
919 // prep round to set initial state.
920 sim.run(1);
921
922 // everyone submits their own ID as a TX and relay it to peers
923 for (Peer* p : validators)
924 p->submit(Tx(static_cast<std::uint32_t>(p->id)));
925
926 sim.run(1);
927
928 // All peers are in sync
929 EXPECT_TRUE(sim.synchronized());
930}
931
932TEST(ConsensusTest, preferred_by_branch)
933{
934 using namespace csf;
935 using namespace std::chrono;
936 SCOPED_TRACE("preferred by branch");
937
938 // Simulate network splits that are prevented from forking when using
939 // preferred ledger by trie. This is a contrived example that involves
940 // excessive network splits, but demonstrates the safety improvement
941 // from the preferred ledger by trie approach.
942
943 // Consider 10 validating nodes that comprise a single common UNL
944 // Ledger history:
945 // 1: A
946 // _/ \_
947 // 2: B C
948 // _/ _/ \_
949 // 3: D C' |||||||| (8 different ledgers)
950
951 // - All nodes generate the common ledger A
952 // - 2 nodes generate B and 8 nodes generate C
953 // - Only 1 of the C nodes sees all the C validations and fully
954 // validates C. The rest of the C nodes split at just the right time
955 // such that they never see any C validations but their own.
956 // - The C nodes continue and generate 8 different child ledgers.
957 // - Meanwhile, the D nodes only saw 1 validation for C and 2
958 // validations
959 // for B.
960 // - The network reconnects and the validations for generation 3 ledgers
961 // are observed (D and the 8 C's)
962 // - In the old approach, 2 votes for D outweighs 1 vote for each C'
963 // so the network would avalanche towards D and fully validate it
964 // EVEN though C was fully validated by one node
965 // - In the new approach, 2 votes for D are not enough to outweight the
966 // 8 implicit votes for C, so nodes will avalanche to C instead
967
968 ConsensusParms const parms{};
969 Sim sim;
970
971 // Goes A->B->D
972 PeerGroup const groupABD = sim.createGroup(2);
973 // Single node that initially fully validates C before the split
974 PeerGroup groupCfast = sim.createGroup(1);
975 // Generates C, but fails to fully validate before the split
976 PeerGroup groupCsplit = sim.createGroup(7);
977
978 PeerGroup groupNotFastC = groupABD + groupCsplit;
979 PeerGroup network = groupABD + groupCsplit + groupCfast;
980
981 SimDuration const delay = round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
982 SimDuration const fDelay = round<milliseconds>(0.1 * parms.ledgerGRANULARITY);
983
984 network.trust(network);
985 // C must have a shorter delay to see all the validations before the
986 // other nodes
987 network.connect(groupCfast, fDelay);
988 // The rest of the network is connected at the same speed
989 groupNotFastC.connect(groupNotFastC, delay);
990
991 Disruptor dc(network, groupCfast, groupCsplit, delay);
992 sim.collectors.add(dc);
993
994 // Consensus round to generate ledger A
995 sim.run(1);
996 EXPECT_TRUE(sim.synchronized());
997
998 // Next round generates B and C
999 // To force B, we inject an extra transaction in to those nodes
1000 for (Peer* peer : groupABD)
1001 {
1002 peer->txInjections.emplace(peer->lastClosedLedger.seq(), Tx{42});
1003 }
1004 // The Disruptor will ensure that nodes disconnect before the C
1005 // validations make it to all but the fastC node
1006 sim.run(1);
1007
1008 // We are no longer in sync, but have not yet forked:
1009 // 9 nodes consider A the last fully validated ledger and fastC sees C
1010 EXPECT_TRUE(!sim.synchronized());
1011 EXPECT_TRUE(sim.branches() == 1);
1012
1013 // Run another round to generate the 8 different C' ledgers
1014 for (Peer* p : network)
1015 p->submit(Tx(static_cast<std::uint32_t>(p->id)));
1016 sim.run(1);
1017
1018 // Still not forked
1019 EXPECT_TRUE(!sim.synchronized());
1020 EXPECT_TRUE(sim.branches() == 1);
1021
1022 // Disruptor will reconnect all but the fastC node
1023 sim.run(1);
1024
1025 EXPECT_TRUE(sim.branches() == 1);
1026 if (sim.branches() == 1)
1027 {
1028 EXPECT_TRUE(sim.synchronized());
1029 }
1030 else // old approach caused a fork
1031 {
1032 EXPECT_TRUE(sim.branches(groupNotFastC) == 1);
1033 EXPECT_TRUE(sim.synchronized(groupNotFastC) == 1);
1034 }
1035}
1036
1037TEST(ConsensusTest, pause_for_laggards)
1038{
1039 using namespace csf;
1040 using namespace std::chrono;
1041 SCOPED_TRACE("pause for laggards");
1042
1043 // Test that validators that jump ahead of the network slow
1044 // down.
1045
1046 // We engineer the following validated ledger history scenario:
1047 //
1048 // / --> B1 --> C1 --> ... -> G1 "ahead"
1049 // A
1050 // \ --> B2 --> C2 "behind"
1051 //
1052 // After validating a common ledger A, a set of "behind" validators
1053 // briefly run slower and validate the lower chain of ledgers.
1054 // The "ahead" validators run normal speed and run ahead validating the
1055 // upper chain of ledgers.
1056 //
1057 // Due to the uncommitted support definition of the preferred branch
1058 // protocol, even if the "behind" validators are a majority, the "ahead"
1059 // validators cannot jump to the proper branch until the "behind"
1060 // validators catch up to the same sequence number. For this test to
1061 // succeed, the ahead validators need to briefly slow down consensus.
1062
1063 ConsensusParms const parms{};
1064 Sim sim;
1065 SimDuration const delay = round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
1066
1067 PeerGroup behind = sim.createGroup(3);
1068 PeerGroup const ahead = sim.createGroup(2);
1069 PeerGroup network = ahead + behind;
1070
1071 hash_set<Peer::NodeKey_t> trustedKeys;
1072 for (Peer const* p : network)
1073 trustedKeys.insert(p->key);
1074 for (Peer* p : network)
1075 p->trustedKeys = trustedKeys;
1076
1077 network.trustAndConnect(network, delay);
1078
1079 // Initial seed round to set prior state
1080 sim.run(1);
1081
1082 // Have the "behind" group initially take a really long time to
1083 // accept a ledger after ending deliberation
1084 for (Peer* p : behind)
1085 p->delays.ledgerAccept = 20s;
1086
1087 // Use the collector to revert the delay after the single
1088 // slow ledger is generated
1089 UndoDelay undoDelay{behind};
1090 sim.collectors.add(undoDelay);
1091
1092 // Run the simulation for 100 seconds of simulation time with
1093 std::chrono::nanoseconds const simDuration = 100s;
1094
1095 // Simulate clients submitting 1 tx every 5 seconds to a random
1096 // validator
1097 Rate const rate{.count = 1, .duration = 5s};
1098 auto peerSelector = makeSelector(
1099 network.begin(), network.end(), std::vector<double>(network.size(), 1.), sim.rng);
1100 auto txSubmitter = makeSubmitter(
1101 ConstantDistribution{rate.inv()},
1102 sim.scheduler.now(),
1103 sim.scheduler.now() + simDuration,
1104 peerSelector,
1105 sim.scheduler,
1106 sim.rng);
1107
1108 // Run simulation
1109 sim.run(simDuration);
1110
1111 // Verify that the network recovered
1112 EXPECT_TRUE(sim.synchronized());
1113}
1114
1115TEST(ConsensusTest, disputes)
1116{
1117 SCOPED_TRACE("disputes");
1118
1119 using namespace csf;
1120
1121 // Test dispute objects directly
1122 using Dispute = CsfDisputedTx;
1123
1124 Tx const txTrue{99};
1125 Tx const txFalse{98};
1126 Tx const txFollowingTrue{97};
1127 Tx const txFollowingFalse{96};
1128 int const numPeers = 100;
1129 ConsensusParms const p;
1130 std::size_t peersUnchanged = 0;
1131
1133
1134 // Three cases:
1135 // 1 proposing, initial vote yes
1136 // 2 proposing, initial vote no
1137 // 3 not proposing, initial vote doesn't matter after the first update,
1138 // use yes
1139 {
1140 Dispute proposingTrue = makeDisputedTx(txTrue, true, numPeers);
1141 Dispute proposingFalse = makeDisputedTx(txFalse, false, numPeers);
1142 Dispute followingTrue = makeDisputedTx(txFollowingTrue, true, numPeers);
1143 Dispute followingFalse = makeDisputedTx(txFollowingFalse, false, numPeers);
1144 EXPECT_TRUE(proposingTrue.id() == 99);
1145 EXPECT_TRUE(proposingFalse.id() == 98);
1146 EXPECT_TRUE(followingTrue.id() == 97);
1147 EXPECT_TRUE(followingFalse.id() == 96);
1148
1149 // Create an even split in the peer votes
1150 for (int i = 0; i < numPeers; ++i)
1151 {
1152 EXPECT_TRUE(proposingTrue.setVote(PeerID(i), i < 50));
1153 EXPECT_TRUE(proposingFalse.setVote(PeerID(i), i < 50));
1154 EXPECT_TRUE(followingTrue.setVote(PeerID(i), i < 50));
1155 EXPECT_TRUE(followingFalse.setVote(PeerID(i), i < 50));
1156 }
1157 // Switch the middle vote to match mine
1158 EXPECT_TRUE(proposingTrue.setVote(PeerID(50), true));
1159 EXPECT_TRUE(proposingFalse.setVote(PeerID(49), false));
1160 EXPECT_TRUE(followingTrue.setVote(PeerID(50), true));
1161 EXPECT_TRUE(followingFalse.setVote(PeerID(49), false));
1162
1163 // no changes yet
1164 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1165 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1166 EXPECT_TRUE(followingTrue.getOurVote() == true);
1167 EXPECT_TRUE(followingFalse.getOurVote() == false);
1168 EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog));
1169 EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog));
1170 EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog));
1171 EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog));
1172 EXPECT_TRUE(clog->str().empty());
1173
1174 // I'm in the majority, my vote should not change
1175 EXPECT_TRUE(!proposingTrue.updateVote(5, true, p));
1176 EXPECT_TRUE(!proposingFalse.updateVote(5, true, p));
1177 EXPECT_TRUE(!followingTrue.updateVote(5, false, p));
1178 EXPECT_TRUE(!followingFalse.updateVote(5, false, p));
1179
1180 EXPECT_TRUE(!proposingTrue.updateVote(10, true, p));
1181 EXPECT_TRUE(!proposingFalse.updateVote(10, true, p));
1182 EXPECT_TRUE(!followingTrue.updateVote(10, false, p));
1183 EXPECT_TRUE(!followingFalse.updateVote(10, false, p));
1184
1185 peersUnchanged = 2;
1186 EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog));
1187 EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog));
1188 EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog));
1189 EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog));
1190 EXPECT_TRUE(clog->str().empty());
1191
1192 // Right now, the vote is 51%. The requirement is about to jump to
1193 // 65%
1194 EXPECT_TRUE(proposingTrue.updateVote(55, true, p));
1195 EXPECT_TRUE(!proposingFalse.updateVote(55, true, p));
1196 EXPECT_TRUE(!followingTrue.updateVote(55, false, p));
1197 EXPECT_TRUE(!followingFalse.updateVote(55, false, p));
1198
1199 EXPECT_TRUE(proposingTrue.getOurVote() == false);
1200 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1201 EXPECT_TRUE(followingTrue.getOurVote() == true);
1202 EXPECT_TRUE(followingFalse.getOurVote() == false);
1203 // 16 validators change their vote to match my original vote
1204 for (int i = 0; i < 16; ++i)
1205 {
1206 auto pTrue = PeerID(numPeers - i - 1);
1207 auto pFalse = PeerID(i);
1208 EXPECT_TRUE(proposingTrue.setVote(pTrue, true));
1209 EXPECT_TRUE(proposingFalse.setVote(pFalse, false));
1210 EXPECT_TRUE(followingTrue.setVote(pTrue, true));
1211 EXPECT_TRUE(followingFalse.setVote(pFalse, false));
1212 }
1213 // The vote should now be 66%, threshold is 65%
1214 EXPECT_TRUE(proposingTrue.updateVote(60, true, p));
1215 EXPECT_TRUE(!proposingFalse.updateVote(60, true, p));
1216 EXPECT_TRUE(!followingTrue.updateVote(60, false, p));
1217 EXPECT_TRUE(!followingFalse.updateVote(60, false, p));
1218
1219 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1220 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1221 EXPECT_TRUE(followingTrue.getOurVote() == true);
1222 EXPECT_TRUE(followingFalse.getOurVote() == false);
1223
1224 // Threshold jumps to 70%
1225 EXPECT_TRUE(proposingTrue.updateVote(86, true, p));
1226 EXPECT_TRUE(!proposingFalse.updateVote(86, true, p));
1227 EXPECT_TRUE(!followingTrue.updateVote(86, false, p));
1228 EXPECT_TRUE(!followingFalse.updateVote(86, false, p));
1229
1230 EXPECT_TRUE(proposingTrue.getOurVote() == false);
1231 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1232 EXPECT_TRUE(followingTrue.getOurVote() == true);
1233 EXPECT_TRUE(followingFalse.getOurVote() == false);
1234
1235 // 5 more validators change their vote to match my original vote
1236 for (int i = 16; i < 21; ++i)
1237 {
1238 auto pTrue = PeerID(numPeers - i - 1);
1239 auto pFalse = PeerID(i);
1240 EXPECT_TRUE(proposingTrue.setVote(pTrue, true));
1241 EXPECT_TRUE(proposingFalse.setVote(pFalse, false));
1242 EXPECT_TRUE(followingTrue.setVote(pTrue, true));
1243 EXPECT_TRUE(followingFalse.setVote(pFalse, false));
1244 }
1245
1246 // The vote should now be 71%, threshold is 70%
1247 EXPECT_TRUE(proposingTrue.updateVote(90, true, p));
1248 EXPECT_TRUE(!proposingFalse.updateVote(90, true, p));
1249 EXPECT_TRUE(!followingTrue.updateVote(90, false, p));
1250 EXPECT_TRUE(!followingFalse.updateVote(90, false, p));
1251
1252 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1253 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1254 EXPECT_TRUE(followingTrue.getOurVote() == true);
1255 EXPECT_TRUE(followingFalse.getOurVote() == false);
1256
1257 // The vote should now be 71%, threshold is 70%
1258 EXPECT_TRUE(!proposingTrue.updateVote(150, true, p));
1259 EXPECT_TRUE(!proposingFalse.updateVote(150, true, p));
1260 EXPECT_TRUE(!followingTrue.updateVote(150, false, p));
1261 EXPECT_TRUE(!followingFalse.updateVote(150, false, p));
1262
1263 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1264 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1265 EXPECT_TRUE(followingTrue.getOurVote() == true);
1266 EXPECT_TRUE(followingFalse.getOurVote() == false);
1267
1268 // The vote should now be 71%, threshold is 70%
1269 EXPECT_TRUE(!proposingTrue.updateVote(190, true, p));
1270 EXPECT_TRUE(!proposingFalse.updateVote(190, true, p));
1271 EXPECT_TRUE(!followingTrue.updateVote(190, false, p));
1272 EXPECT_TRUE(!followingFalse.updateVote(190, false, p));
1273
1274 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1275 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1276 EXPECT_TRUE(followingTrue.getOurVote() == true);
1277 EXPECT_TRUE(followingFalse.getOurVote() == false);
1278
1279 peersUnchanged = 3;
1280 EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog));
1281 EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog));
1282 EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog));
1283 EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog));
1284 EXPECT_TRUE(clog->str().empty());
1285
1286 // Threshold jumps to 95%
1287 EXPECT_TRUE(proposingTrue.updateVote(220, true, p));
1288 EXPECT_TRUE(!proposingFalse.updateVote(220, true, p));
1289 EXPECT_TRUE(!followingTrue.updateVote(220, false, p));
1290 EXPECT_TRUE(!followingFalse.updateVote(220, false, p));
1291
1292 EXPECT_TRUE(proposingTrue.getOurVote() == false);
1293 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1294 EXPECT_TRUE(followingTrue.getOurVote() == true);
1295 EXPECT_TRUE(followingFalse.getOurVote() == false);
1296
1297 // 25 more validators change their vote to match my original vote
1298 for (int i = 21; i < 46; ++i)
1299 {
1300 auto pTrue = PeerID(numPeers - i - 1);
1301 auto pFalse = PeerID(i);
1302 EXPECT_TRUE(proposingTrue.setVote(pTrue, true));
1303 EXPECT_TRUE(proposingFalse.setVote(pFalse, false));
1304 EXPECT_TRUE(followingTrue.setVote(pTrue, true));
1305 EXPECT_TRUE(followingFalse.setVote(pFalse, false));
1306 }
1307
1308 // The vote should now be 96%, threshold is 95%
1309 EXPECT_TRUE(proposingTrue.updateVote(250, true, p));
1310 EXPECT_TRUE(!proposingFalse.updateVote(250, true, p));
1311 EXPECT_TRUE(!followingTrue.updateVote(250, false, p));
1312 EXPECT_TRUE(!followingFalse.updateVote(250, false, p));
1313
1314 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1315 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1316 EXPECT_TRUE(followingTrue.getOurVote() == true);
1317 EXPECT_TRUE(followingFalse.getOurVote() == false);
1318
1319 for (peersUnchanged = 0; peersUnchanged < 6; ++peersUnchanged)
1320 {
1321 EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog));
1322 EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog));
1323 EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog));
1324 EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog));
1325 EXPECT_TRUE(clog->str().empty());
1326 }
1327
1328 auto expectStalled = [&clog](
1329 int txid,
1330 bool ourVote,
1331 int ourTime,
1332 int peerTime,
1333 int support,
1334 std::uint32_t line) {
1335 using namespace std::string_literals;
1336
1337 auto const s = clog->str();
1338 SCOPED_TRACE(::testing::Message() << __FILE__ << ":" << line);
1339 EXPECT_NE(s.find("stalled"), s.npos) << s;
1340 EXPECT_TRUE(s.starts_with("Transaction "s + std::to_string(txid))) << s;
1341 EXPECT_NE(s.find("voting "s + (ourVote ? "YES" : "NO")), s.npos) << s;
1342 EXPECT_NE(s.find("for "s + std::to_string(ourTime) + " rounds."s), s.npos) << s;
1343 EXPECT_NE(s.find("votes in "s + std::to_string(peerTime) + " rounds."), s.npos) << s;
1344 EXPECT_TRUE(s.ends_with("has "s + std::to_string(support) + "% support. "s)) << s;
1346 };
1347
1348 for (int i = 0; i < 1; ++i)
1349 {
1350 EXPECT_TRUE(!proposingTrue.updateVote(250 + (10 * i), true, p));
1351 EXPECT_TRUE(!proposingFalse.updateVote(250 + (10 * i), true, p));
1352 EXPECT_TRUE(!followingTrue.updateVote(250 + (10 * i), false, p));
1353 EXPECT_TRUE(!followingFalse.updateVote(250 + (10 * i), false, p));
1354
1355 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1356 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1357 EXPECT_TRUE(followingTrue.getOurVote() == true);
1358 EXPECT_TRUE(followingFalse.getOurVote() == false);
1359
1360 // true vote has changed recently, so not stalled
1361 EXPECT_TRUE(!isStalled(proposingTrue, p, true, 0, clog));
1362 EXPECT_TRUE(clog->str().empty());
1363 // remaining votes have been unchanged in so long that we only
1364 // need to hit the second round at 95% to be stalled, regardless
1365 // of peers
1366 EXPECT_TRUE(isStalled(proposingFalse, p, true, 0, clog));
1367 expectStalled(98, false, 11, 0, 2, __LINE__);
1368 EXPECT_TRUE(isStalled(followingTrue, p, false, 0, clog));
1369 expectStalled(97, true, 11, 0, 97, __LINE__);
1370 EXPECT_TRUE(isStalled(followingFalse, p, false, 0, clog));
1371 expectStalled(96, false, 11, 0, 3, __LINE__);
1372
1373 // true vote has changed recently, so not stalled
1374 EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog));
1375 EXPECT_TRUE(clog->str().empty()) << clog->str();
1376 // remaining votes have been unchanged in so long that we only
1377 // need to hit the second round at 95% to be stalled, regardless
1378 // of peers
1379 EXPECT_TRUE(isStalled(proposingFalse, p, true, peersUnchanged, clog));
1380 expectStalled(98, false, 11, 6, 2, __LINE__);
1381 EXPECT_TRUE(isStalled(followingTrue, p, false, peersUnchanged, clog));
1382 expectStalled(97, true, 11, 6, 97, __LINE__);
1383 EXPECT_TRUE(isStalled(followingFalse, p, false, peersUnchanged, clog));
1384 expectStalled(96, false, 11, 6, 3, __LINE__);
1385 }
1386 for (int i = 1; i < 3; ++i)
1387 {
1388 EXPECT_TRUE(!proposingTrue.updateVote(250 + (10 * i), true, p));
1389 EXPECT_TRUE(!proposingFalse.updateVote(250 + (10 * i), true, p));
1390 EXPECT_TRUE(!followingTrue.updateVote(250 + (10 * i), false, p));
1391 EXPECT_TRUE(!followingFalse.updateVote(250 + (10 * i), false, p));
1392
1393 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1394 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1395 EXPECT_TRUE(followingTrue.getOurVote() == true);
1396 EXPECT_TRUE(followingFalse.getOurVote() == false);
1397
1398 // true vote changed 2 rounds ago, and peers are changing, so
1399 // not stalled
1400 EXPECT_TRUE(!isStalled(proposingTrue, p, true, 0, clog));
1401 EXPECT_TRUE(clog->str().empty()) << clog->str();
1402 // still stalled
1403 EXPECT_TRUE(isStalled(proposingFalse, p, true, 0, clog));
1404 expectStalled(98, false, 11 + i, 0, 2, __LINE__);
1405 EXPECT_TRUE(isStalled(followingTrue, p, false, 0, clog));
1406 expectStalled(97, true, 11 + i, 0, 97, __LINE__);
1407 EXPECT_TRUE(isStalled(followingFalse, p, false, 0, clog));
1408 expectStalled(96, false, 11 + i, 0, 3, __LINE__);
1409
1410 // true vote changed 2 rounds ago, and peers are NOT changing,
1411 // so stalled
1412 EXPECT_TRUE(isStalled(proposingTrue, p, true, peersUnchanged, clog));
1413 expectStalled(99, true, 1 + i, 6, 97, __LINE__);
1414 // still stalled
1415 EXPECT_TRUE(isStalled(proposingFalse, p, true, peersUnchanged, clog));
1416 expectStalled(98, false, 11 + i, 6, 2, __LINE__);
1417 EXPECT_TRUE(isStalled(followingTrue, p, false, peersUnchanged, clog));
1418 expectStalled(97, true, 11 + i, 6, 97, __LINE__);
1419 EXPECT_TRUE(isStalled(followingFalse, p, false, peersUnchanged, clog));
1420 expectStalled(96, false, 11 + i, 6, 3, __LINE__);
1421 }
1422 for (int i = 3; i < 5; ++i)
1423 {
1424 EXPECT_TRUE(!proposingTrue.updateVote(250 + (10 * i), true, p));
1425 EXPECT_TRUE(!proposingFalse.updateVote(250 + (10 * i), true, p));
1426 EXPECT_TRUE(!followingTrue.updateVote(250 + (10 * i), false, p));
1427 EXPECT_TRUE(!followingFalse.updateVote(250 + (10 * i), false, p));
1428
1429 EXPECT_TRUE(proposingTrue.getOurVote() == true);
1430 EXPECT_TRUE(proposingFalse.getOurVote() == false);
1431 EXPECT_TRUE(followingTrue.getOurVote() == true);
1432 EXPECT_TRUE(followingFalse.getOurVote() == false);
1433
1434 EXPECT_TRUE(isStalled(proposingTrue, p, true, 0, clog));
1435 expectStalled(99, true, 1 + i, 0, 97, __LINE__);
1436 EXPECT_TRUE(isStalled(proposingFalse, p, true, 0, clog));
1437 expectStalled(98, false, 11 + i, 0, 2, __LINE__);
1438 EXPECT_TRUE(isStalled(followingTrue, p, false, 0, clog));
1439 expectStalled(97, true, 11 + i, 0, 97, __LINE__);
1440 EXPECT_TRUE(isStalled(followingFalse, p, false, 0, clog));
1441 expectStalled(96, false, 11 + i, 0, 3, __LINE__);
1442
1443 EXPECT_TRUE(isStalled(proposingTrue, p, true, peersUnchanged, clog));
1444 expectStalled(99, true, 1 + i, 6, 97, __LINE__);
1445 EXPECT_TRUE(isStalled(proposingFalse, p, true, peersUnchanged, clog));
1446 expectStalled(98, false, 11 + i, 6, 2, __LINE__);
1447 EXPECT_TRUE(isStalled(followingTrue, p, false, peersUnchanged, clog));
1448 expectStalled(97, true, 11 + i, 6, 97, __LINE__);
1449 EXPECT_TRUE(isStalled(followingFalse, p, false, peersUnchanged, clog));
1450 expectStalled(96, false, 11 + i, 6, 3, __LINE__);
1451 }
1452 }
1453}
1454
1455} // namespace xrpl::test
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
std::chrono::duration< rep, period > duration
Definition chrono.h:47
static TestSink & instance()
Definition TestSink.h:12
TaggedInteger< std::uint32_t, SeqTag > Seq
Definition ledgers.h:53
A single transaction.
Definition Tx.h:24
T insert(T... args)
T make_unique(T... args)
Result split(FwdIt first, FwdIt last, Char delim)
Parse a character sequence of values separated by commas.
Definition rfc2616.h:107
SimClock::duration SimDuration
Definition SimTime.h:14
TaggedInteger< std::uint32_t, PeerIDTag > PeerID
Definition Validation.h:17
SimClock::time_point SimTime
Definition SimTime.h:15
json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition rate.cpp:15
TEST(UnitsTest, types)
Definition Units.cpp:16
constexpr auto kIncreaseLedgerTimeResolutionEvery
How often we increase the close time resolution (in numbers of ledgers).
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
ConsensusState
Whether we have or don't have a consensus.
@ Expired
Consensus time limit has hard-expired.
@ MovedOn
The network has consensus without us.
@ Yes
We have consensus along with the network.
@ No
We do not have consensus.
ConsensusState checkConsensus(std::size_t prevProposers, std::size_t currentProposers, std::size_t currentAgree, std::size_t currentFinished, std::chrono::milliseconds previousAgreeTime, std::chrono::milliseconds currentAgreeTime, bool stalled, ConsensusParms const &parms, bool proposing, beast::Journal j, std::unique_ptr< std::stringstream > const &clog={})
Determine whether the network reached consensus and whether we joined.
bool shouldCloseLedger(bool anyTransactions, std::size_t prevProposers, std::size_t proposersClosed, std::size_t proposersValidated, std::chrono::milliseconds prevRoundTime, std::chrono::milliseconds timeSincePrevClose, std::chrono::milliseconds openTime, std::chrono::milliseconds idleInterval, ConsensusParms const &parms, beast::Journal j, std::unique_ptr< std::stringstream > const &clog={})
Determines whether the current ledger should close at this time.
T round(T... args)
Consensus algorithm parameters.
std::chrono::milliseconds const ledgerMinClose
Minimum number of seconds to wait to ensure others have computed the LCL.
std::chrono::seconds const proposeFRESHNESS
How long we consider a proposal fresh.
std::chrono::milliseconds const ledgerGRANULARITY
How often we check state or change positions.
std::size_t const minConsensusPct
The percentage threshold above which we can declare consensus.
Represents a transfer rate.
Definition Rate.h:21
T to_string(T... args)