diff --git a/.cspell.config.yaml b/.cspell.config.yaml index c862379f08..9cd8417362 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -30,7 +30,9 @@ ignoreRegExpList: - ABCDEFGHIJKLMNOPQRSTUVWXYZ - ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz overrides: - - filename: "**/*_test.cpp" # all test files + - filename: + - "**/*_test.cpp" # legacy boost.test files + - "src/tests/**/*.cpp" # gtest test files ignoreRegExpList: - /"[^"]*"/g # double-quoted strings - /'[^']*'/g # single-quoted strings diff --git a/.github/scripts/levelization/README.md b/.github/scripts/levelization/README.md index f657344827..93748c43e1 100644 --- a/.github/scripts/levelization/README.md +++ b/.github/scripts/levelization/README.md @@ -40,18 +40,18 @@ listed later. | 04 | xrpl/protocol | | 05 | xrpl/core xrpl/resource xrpl/server | | 06 | xrpl/ledger xrpl/nodestore xrpl/net | -| 07 | xrpl/shamap | +| 07 | xrpl/shamap xrpl/consensus | ## xrpld Modules (Application Implementation) -| Level / Tier | Module(s) | -| ------------ | -------------------------------- | -| 05 | xrpld/conditions xrpld/consensus | -| 06 | xrpld/core xrpld/peerfinder | -| 07 | xrpld/shamap xrpld/overlay | -| 08 | xrpld/app | -| 09 | xrpld/rpc | -| 10 | xrpld/perflog | +| Level / Tier | Module(s) | +| ------------ | --------------------------- | +| 05 | xrpld/conditions | +| 06 | xrpld/core xrpld/peerfinder | +| 07 | xrpld/shamap xrpld/overlay | +| 08 | xrpld/app | +| 09 | xrpld/rpc | +| 10 | xrpld/perflog | ## Test Modules diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index fdd134dc8a..709ba4d6d4 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -6,6 +6,8 @@ libxrpl.conditions > xrpl.basics libxrpl.conditions > xrpl.conditions libxrpl.config > xrpl.basics libxrpl.config > xrpl.config +libxrpl.consensus > xrpl.basics +libxrpl.consensus > xrpl.consensus libxrpl.core > xrpl.basics libxrpl.core > xrpl.core libxrpl.core > xrpl.json @@ -63,9 +65,9 @@ test.app > test.jtx test.app > test.unit_test test.app > xrpl.basics test.app > xrpl.config +test.app > xrpl.consensus test.app > xrpl.core test.app > xrpld.app -test.app > xrpld.consensus test.app > xrpld.core test.app > xrpld.overlay test.app > xrpld.rpc @@ -86,12 +88,9 @@ test.basics > xrpl.protocol test.beast > xrpl.basics test.conditions > xrpl.basics test.conditions > xrpl.conditions -test.consensus > test.csf test.consensus > test.jtx -test.consensus > test.unit_test test.consensus > xrpl.basics test.consensus > xrpld.app -test.consensus > xrpld.consensus test.consensus > xrpl.ledger test.consensus > xrpl.protocol test.consensus > xrpl.shamap @@ -106,10 +105,6 @@ test.core > xrpl.json test.core > xrpl.protocol test.core > xrpl.rdb test.core > xrpl.server -test.csf > xrpl.basics -test.csf > xrpld.consensus -test.csf > xrpl.json -test.csf > xrpl.ledger test.json > test.jtx test.json > xrpl.json test.jtx > test.unit_test @@ -189,6 +184,7 @@ test.unit_test > xrpl.basics test.unit_test > xrpl.protocol tests.libxrpl > xrpl.basics tests.libxrpl > xrpl.config +tests.libxrpl > xrpl.consensus tests.libxrpl > xrpl.core tests.libxrpl > xrpl.json tests.libxrpl > xrpl.ledger @@ -204,6 +200,10 @@ tests.libxrpl > xrpl.tx xrpl.conditions > xrpl.basics xrpl.conditions > xrpl.protocol xrpl.config > xrpl.basics +xrpl.consensus > xrpl.basics +xrpl.consensus > xrpl.json +xrpl.consensus > xrpl.ledger +xrpl.consensus > xrpl.protocol xrpl.core > xrpl.basics xrpl.core > xrpl.json xrpl.core > xrpl.protocol @@ -246,8 +246,8 @@ xrpl.tx > xrpl.protocol xrpld.app > test.unit_test xrpld.app > xrpl.basics xrpld.app > xrpl.config +xrpld.app > xrpl.consensus xrpld.app > xrpl.core -xrpld.app > xrpld.consensus xrpld.app > xrpld.core xrpld.app > xrpl.json xrpld.app > xrpl.ledger @@ -260,10 +260,6 @@ xrpld.app > xrpl.resource xrpld.app > xrpl.server xrpld.app > xrpl.shamap xrpld.app > xrpl.tx -xrpld.consensus > xrpl.basics -xrpld.consensus > xrpl.json -xrpld.consensus > xrpl.ledger -xrpld.consensus > xrpl.protocol xrpld.core > xrpl.basics xrpld.core > xrpl.config xrpld.core > xrpl.core @@ -272,8 +268,8 @@ xrpld.core > xrpl.protocol xrpld.core > xrpl.rdb xrpld.overlay > xrpl.basics xrpld.overlay > xrpl.config +xrpld.overlay > xrpl.consensus xrpld.overlay > xrpl.core -xrpld.overlay > xrpld.consensus xrpld.overlay > xrpld.core xrpld.overlay > xrpld.peerfinder xrpld.overlay > xrpl.json diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 62a8fe143b..a3e08145d5 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -207,6 +207,16 @@ target_link_libraries( add_module(xrpl tx) target_link_libraries(xrpl.libxrpl.tx PUBLIC xrpl.libxrpl.ledger) +add_module(xrpl consensus) +target_link_libraries( + xrpl.libxrpl.consensus + PUBLIC + xrpl.libxrpl.basics + xrpl.libxrpl.json + xrpl.libxrpl.protocol + xrpl.libxrpl.ledger +) + add_library(xrpl.libxrpl) set_target_properties(xrpl.libxrpl PROPERTIES OUTPUT_NAME xrpl) @@ -226,6 +236,7 @@ target_link_modules( beast conditions config + consensus core crypto git diff --git a/src/xrpld/app/consensus/RCLCensorshipDetector.h b/include/xrpl/consensus/CensorshipDetector.h similarity index 98% rename from src/xrpld/app/consensus/RCLCensorshipDetector.h rename to include/xrpl/consensus/CensorshipDetector.h index 6d0e20031e..3d2708f68f 100644 --- a/src/xrpld/app/consensus/RCLCensorshipDetector.h +++ b/include/xrpl/consensus/CensorshipDetector.h @@ -10,7 +10,7 @@ namespace xrpl { template -class RCLCensorshipDetector +class CensorshipDetector { public: struct TxIDSeq @@ -49,7 +49,7 @@ private: TxIDSeqVec tracker_; public: - RCLCensorshipDetector() = default; + CensorshipDetector() = default; /** * Add transactions being proposed for the current consensus round. diff --git a/src/xrpld/consensus/Consensus.h b/include/xrpl/consensus/Consensus.h similarity index 99% rename from src/xrpld/consensus/Consensus.h rename to include/xrpl/consensus/Consensus.h index 440191939b..f9d5f7ef02 100644 --- a/src/xrpld/consensus/Consensus.h +++ b/include/xrpl/consensus/Consensus.h @@ -1,15 +1,14 @@ #pragma once -#include -#include -#include - #include #include #include #include #include #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/consensus/ConsensusParms.h b/include/xrpl/consensus/ConsensusParms.h similarity index 100% rename from src/xrpld/consensus/ConsensusParms.h rename to include/xrpl/consensus/ConsensusParms.h diff --git a/src/xrpld/consensus/ConsensusProposal.h b/include/xrpl/consensus/ConsensusProposal.h similarity index 100% rename from src/xrpld/consensus/ConsensusProposal.h rename to include/xrpl/consensus/ConsensusProposal.h diff --git a/src/xrpld/consensus/ConsensusTypes.h b/include/xrpl/consensus/ConsensusTypes.h similarity index 98% rename from src/xrpld/consensus/ConsensusTypes.h rename to include/xrpl/consensus/ConsensusTypes.h index 4553e46f48..4dac2d9912 100644 --- a/src/xrpld/consensus/ConsensusTypes.h +++ b/include/xrpl/consensus/ConsensusTypes.h @@ -1,11 +1,10 @@ #pragma once -#include -#include - #include #include #include +#include +#include #include #include diff --git a/src/xrpld/consensus/DisputedTx.h b/include/xrpl/consensus/DisputedTx.h similarity index 99% rename from src/xrpld/consensus/DisputedTx.h rename to include/xrpl/consensus/DisputedTx.h index 12ed00d460..c194716d43 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/include/xrpl/consensus/DisputedTx.h @@ -1,9 +1,8 @@ #pragma once -#include - #include #include +#include #include #include diff --git a/src/xrpld/consensus/LedgerTrie.h b/include/xrpl/consensus/LedgerTrie.h similarity index 100% rename from src/xrpld/consensus/LedgerTrie.h rename to include/xrpl/consensus/LedgerTrie.h diff --git a/src/xrpld/consensus/README.md b/include/xrpl/consensus/README.md similarity index 100% rename from src/xrpld/consensus/README.md rename to include/xrpl/consensus/README.md diff --git a/src/xrpld/consensus/Validations.h b/include/xrpl/consensus/Validations.h similarity index 99% rename from src/xrpld/consensus/Validations.h rename to include/xrpl/consensus/Validations.h index 2696804c86..ebb13c5e7c 100644 --- a/src/xrpld/consensus/Validations.h +++ b/include/xrpl/consensus/Validations.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -11,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/consensus/Consensus.cpp b/src/libxrpl/consensus/Consensus.cpp similarity index 98% rename from src/xrpld/consensus/Consensus.cpp rename to src/libxrpl/consensus/Consensus.cpp index d529ab2e44..6f398cf66c 100644 --- a/src/xrpld/consensus/Consensus.cpp +++ b/src/libxrpl/consensus/Consensus.cpp @@ -1,10 +1,9 @@ -#include - -#include -#include +#include #include #include +#include +#include #include #include diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index aaf84225a7..9ace2e21af 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -2,12 +2,12 @@ #include #include -#include #include #include #include #include +#include #include #include #include diff --git a/src/test/consensus/ByzantineFailureSim_test.cpp b/src/test/consensus/ByzantineFailureSim_test.cpp deleted file mode 100644 index c3c51125b5..0000000000 --- a/src/test/consensus/ByzantineFailureSim_test.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - -namespace xrpl::test { - -class ByzantineFailureSim_test : public beast::unit_test::Suite -{ - void - run() override - { - using namespace csf; - using namespace std::chrono; - - // This test simulates a specific topology with nodes generating - // different ledgers due to a simulated byzantine failure (injecting - // an extra non-consensus transaction). - - Sim sim; - ConsensusParms const parms{}; - - SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); - PeerGroup a = sim.createGroup(1); - PeerGroup b = sim.createGroup(1); - PeerGroup c = sim.createGroup(1); - PeerGroup d = sim.createGroup(1); - PeerGroup e = sim.createGroup(1); - PeerGroup f = sim.createGroup(1); - PeerGroup g = sim.createGroup(1); - - a.trustAndConnect(a + b + c + g, delay); - b.trustAndConnect(b + a + c + d + e, delay); - c.trustAndConnect(c + a + b + d + e, delay); - d.trustAndConnect(d + b + c + e + f, delay); - e.trustAndConnect(e + b + c + d + f, delay); - f.trustAndConnect(f + d + e + g, delay); - g.trustAndConnect(g + a + f, delay); - - PeerGroup const network = a + b + c + d + e + f + g; - - StreamCollector sc{std::cout}; - - sim.collectors.add(sc); - - for (TrustGraph::ForkInfo const& fi : sim.trustGraph.forkablePairs(0.8)) - { - std::cout << "Can fork " << PeerGroup{fi.unlA} << " " - << " " << PeerGroup{fi.unlB} << " overlap " << fi.overlap << " required " - << fi.required << "\n"; - }; - - // set prior state - sim.run(1); - - PeerGroup byzantineNodes = a + b + c + g; - // All peers see some TX 0 - for (Peer* peer : network) - { - peer->submit(Tx{0}); - // Peers 0,1,2,6 will close the next ledger differently by injecting - // a non-consensus approved transaction - if (byzantineNodes.contains(peer)) - { - peer->txInjections.emplace(peer->lastClosedLedger.seq(), Tx{42}); - } - } - sim.run(4); - std::cout << "Branches: " << sim.branches() << "\n"; - std::cout << "Fully synchronized: " << std::boolalpha << sim.synchronized() << "\n"; - // Not tessting anything currently. - pass(); - } -}; - -BEAST_DEFINE_TESTSUITE_MANUAL(ByzantineFailureSim, consensus, xrpl); - -} // namespace xrpl::test diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp deleted file mode 100644 index 45f58d16ba..0000000000 --- a/src/test/consensus/Consensus_test.cpp +++ /dev/null @@ -1,1432 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl::test { - -class Consensus_test : public beast::unit_test::Suite -{ - SuiteJournal journal_; - -public: - Consensus_test() : journal_("Consensus_test", *this) - { - } - - void - testShouldCloseLedger() - { - using namespace std::chrono_literals; - testcase("should close ledger"); - - // Use default parameters - ConsensusParms const p{}; - - // Bizarre times forcibly close - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, p, journal_)); - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, p, journal_)); - BEAST_EXPECT(shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, p, journal_)); - - // Rest of network has closed - BEAST_EXPECT(shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, p, journal_)); - - // No transactions means wait until end of internval - BEAST_EXPECT(!shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, p, journal_)); - BEAST_EXPECT(shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, p, journal_)); - - // Enforce minimum ledger open time - BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, p, journal_)); - - // Don't go too much faster than last time - BEAST_EXPECT(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, p, journal_)); - - BEAST_EXPECT(shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, p, journal_)); - } - - void - testCheckConsensus() - { - using namespace std::chrono_literals; - testcase("check consensus"); - - // Use default parameters - ConsensusParms const p{}; - - /////////////// - // Disputes still in doubt - // - // Not enough time has elapsed - BEAST_EXPECT( - ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, false, p, true, journal_)); - - // If not enough peers have proposed, ensure - // more time for proposals - BEAST_EXPECT( - ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 4s, false, p, true, journal_)); - - // Enough time has elapsed and we all agree - BEAST_EXPECT( - ConsensusState::Yes == checkConsensus(10, 2, 2, 0, 3s, 10s, false, p, true, journal_)); - - // Enough time has elapsed and we don't yet agree - BEAST_EXPECT( - ConsensusState::No == checkConsensus(10, 2, 1, 0, 3s, 10s, false, p, true, journal_)); - - // Our peers have moved on - // Enough time has elapsed and we all agree - BEAST_EXPECT( - ConsensusState::MovedOn == - checkConsensus(10, 2, 1, 8, 3s, 10s, false, p, true, journal_)); - - // If no peers, don't agree until time has passed. - BEAST_EXPECT( - ConsensusState::No == checkConsensus(0, 0, 0, 0, 3s, 10s, false, p, true, journal_)); - - // Agree if no peers and enough time has passed. - BEAST_EXPECT( - ConsensusState::Yes == checkConsensus(0, 0, 0, 0, 3s, 16s, false, p, true, journal_)); - - // Expire if too much time has passed without agreement - BEAST_EXPECT( - ConsensusState::Expired == - checkConsensus(10, 8, 1, 0, 1s, 19s, false, p, true, journal_)); - - /////////////// - // Stalled - // - // Not enough time has elapsed - BEAST_EXPECT( - ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, true, p, true, journal_)); - - // If not enough peers have proposed, ensure - // more time for proposals - BEAST_EXPECT( - ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 4s, true, p, true, journal_)); - - // Enough time has elapsed and we all agree - BEAST_EXPECT( - ConsensusState::Yes == checkConsensus(10, 2, 2, 0, 3s, 10s, true, p, true, journal_)); - - // Enough time has elapsed and we don't yet agree, but there's nothing - // left to dispute - BEAST_EXPECT( - ConsensusState::Yes == checkConsensus(10, 2, 1, 0, 3s, 10s, true, p, true, journal_)); - - // Our peers have moved on - // Enough time has elapsed and we all agree, nothing left to dispute - BEAST_EXPECT( - ConsensusState::Yes == checkConsensus(10, 2, 1, 8, 3s, 10s, true, p, true, journal_)); - - // If no peers, don't agree until time has passed. - BEAST_EXPECT( - ConsensusState::No == checkConsensus(0, 0, 0, 0, 3s, 10s, true, p, true, journal_)); - - // Agree if no peers and enough time has passed. - BEAST_EXPECT( - ConsensusState::Yes == checkConsensus(0, 0, 0, 0, 3s, 16s, true, p, true, journal_)); - - // We are done if there's nothing left to dispute, no matter how much - // time has passed - BEAST_EXPECT( - ConsensusState::Yes == checkConsensus(10, 8, 1, 0, 1s, 19s, true, p, true, journal_)); - } - - void - testStandalone() - { - using namespace std::chrono_literals; - using namespace csf; - testcase("standalone"); - - Sim s; - PeerGroup const peers = s.createGroup(1); - Peer* peer = peers[0]; - peer->targetLedgers = 1; - peer->start(); - peer->submit(Tx{1}); - - s.scheduler.step(); - - // Inspect that the proper ledger was created - auto const& lcl = peer->lastClosedLedger; - BEAST_EXPECT(peer->prevLedgerID() == lcl.id()); - BEAST_EXPECT(lcl.seq() == Ledger::Seq{1}); - BEAST_EXPECT(lcl.txs().size() == 1); - BEAST_EXPECT(lcl.txs().contains(Tx{1})); - BEAST_EXPECT(peer->prevProposers == 0); - } - - void - testPeersAgree() - { - using namespace csf; - using namespace std::chrono; - testcase("peers agree"); - - ConsensusParms const parms{}; - Sim sim; - PeerGroup peers = sim.createGroup(5); - - // Connected trust and network graphs with single fixed delay - peers.trustAndConnect(peers, round(0.2 * parms.ledgerGRANULARITY)); - - // everyone submits their own ID as a TX - for (Peer* p : peers) - p->submit(Tx(static_cast(p->id))); - - sim.run(1); - - // All peers are in sync - if (BEAST_EXPECT(sim.synchronized())) - { - for (Peer const* peer : peers) - { - auto const& lcl = peer->lastClosedLedger; - BEAST_EXPECT(lcl.id() == peer->prevLedgerID()); - BEAST_EXPECT(lcl.seq() == Ledger::Seq{1}); - // All peers proposed - BEAST_EXPECT(peer->prevProposers == peers.size() - 1); - // All transactions were accepted - for (std::uint32_t i = 0; i < peers.size(); ++i) - BEAST_EXPECT(lcl.txs().contains(Tx{i})); - } - } - } - - void - testSlowPeers() - { - using namespace csf; - using namespace std::chrono; - testcase("slow peers"); - - // Several tests of a complete trust graph with a subset of peers - // that have significantly longer network delays to the rest of the - // network - - // Test when a slow peer doesn't delay a consensus quorum (4/5 agree) - { - ConsensusParms const parms{}; - Sim sim; - PeerGroup slow = sim.createGroup(1); - PeerGroup fast = sim.createGroup(4); - PeerGroup network = fast + slow; - - // Fully connected trust graph - network.trust(network); - - // Fast and slow network connections - fast.connect(fast, round(0.2 * parms.ledgerGRANULARITY)); - - slow.connect(network, round(1.1 * parms.ledgerGRANULARITY)); - - // All peers submit their own ID as a transaction - for (Peer* peer : network) - peer->submit(Tx{static_cast(peer->id)}); - - sim.run(1); - - // Verify all peers have same LCL but are missing transaction 0 - // All peers are in sync even with a slower peer 0 - if (BEAST_EXPECT(sim.synchronized())) - { - for (Peer const* peer : network) - { - auto const& lcl = peer->lastClosedLedger; - BEAST_EXPECT(lcl.id() == peer->prevLedgerID()); - BEAST_EXPECT(lcl.seq() == Ledger::Seq{1}); - - BEAST_EXPECT(peer->prevProposers == network.size() - 1); - BEAST_EXPECT(peer->prevRoundTime == network[0]->prevRoundTime); - - BEAST_EXPECT(not lcl.txs().contains(Tx{0})); - for (std::uint32_t i = 2; i < network.size(); ++i) - BEAST_EXPECT(lcl.txs().contains(Tx{i})); - - // Tx 0 didn't make it - BEAST_EXPECT(peer->openTxs.contains(Tx{0})); - } - } - } - - // Test when the slow peers delay a consensus quorum (4/6 agree) - { - // Run two tests - // 1. The slow peers are participating in consensus - // 2. The slow peers are just observing - - for (auto isParticipant : {true, false}) - { - ConsensusParms const parms{}; - - Sim sim; - PeerGroup slow = sim.createGroup(2); - PeerGroup fast = sim.createGroup(4); - PeerGroup network = fast + slow; - - // Connected trust graph - network.trust(network); - - // Fast and slow network connections - fast.connect(fast, round(0.2 * parms.ledgerGRANULARITY)); - - slow.connect(network, round(1.1 * parms.ledgerGRANULARITY)); - - for (Peer* peer : slow) - peer->runAsValidator = isParticipant; - - // All peers submit their own ID as a transaction and relay it - // to peers - for (Peer* peer : network) - peer->submit(Tx{static_cast(peer->id)}); - - sim.run(1); - - if (BEAST_EXPECT(sim.synchronized())) - { - // Verify all peers have same LCL but are missing - // transaction 0,1 which was not received by all peers - // before the ledger closed - for (Peer const* peer : network) - { - // Closed ledger has all but transaction 0,1 - auto const& lcl = peer->lastClosedLedger; - BEAST_EXPECT(lcl.seq() == Ledger::Seq{1}); - BEAST_EXPECT(not lcl.txs().contains(Tx{0})); - BEAST_EXPECT(not lcl.txs().contains(Tx{1})); - for (std::uint32_t i = slow.size(); i < network.size(); ++i) - BEAST_EXPECT(lcl.txs().contains(Tx{i})); - - // Tx 0-1 didn't make it - BEAST_EXPECT(peer->openTxs.contains(Tx{0})); - BEAST_EXPECT(peer->openTxs.contains(Tx{1})); - } - - Peer const* slowPeer = slow[0]; - if (isParticipant) - { - BEAST_EXPECT(slowPeer->prevProposers == network.size() - 1); - } - else - { - BEAST_EXPECT(slowPeer->prevProposers == fast.size()); - } - - for (Peer const* peer : fast) - { - // Due to the network link delay settings - // Peer 0 initially proposes {0} - // Peer 1 initially proposes {1} - // Peers 2-5 initially propose {2,3,4,5} - // Since peers 2-5 agree, 4/6 > the initial 50% needed - // to include a disputed transaction, so Peer 0/1 switch - // to agree with those peers. Peer 0/1 then closes with - // an 80% quorum of agreeing positions (5/6) match. - // - // Peers 2-5 do not change position, since tx 0 or tx 1 - // have less than the 50% initial threshold. They also - // cannot declare consensus, since 4/6 agreeing - // positions are < 80% threshold. They therefore need an - // additional timerEntry call to see the updated - // positions from Peer 0 & 1. - - if (isParticipant) - { - BEAST_EXPECT(peer->prevProposers == network.size() - 1); - BEAST_EXPECT(peer->prevRoundTime > slowPeer->prevRoundTime); - } - else - { - BEAST_EXPECT(peer->prevProposers == fast.size() - 1); - // so all peers should have closed together - BEAST_EXPECT(peer->prevRoundTime == slowPeer->prevRoundTime); - } - } - } - } - } - } - - void - testCloseTimeDisagree() - { - using namespace csf; - using namespace std::chrono; - testcase("close time disagree"); - - // This is a very specialized test to get ledgers to disagree on - // the close time. It unfortunately assumes knowledge about current - // timing constants. This is a necessary evil to get coverage up - // pending more extensive refactorings of timing constants. - - // In order to agree-to-disagree on the close time, there must be no - // clear majority of nodes agreeing on a close time. This test - // sets a relative offset to the peers internal clocks so that they - // send proposals with differing times. - - // However, agreement is on the effective close time, not the - // exact close time. The minimum closeTimeResolution is given by - // ledgerPossibleTimeResolutions[0], which is currently 10s. This means - // the skews need to be at least 10 seconds to have different effective - // close times. - - // Complicating this matter is that nodes will ignore proposals - // with times more than proposeFRESHNESS =20s in the past. So at - // the minimum granularity, we have at most 3 types of skews - // (0s,10s,20s). - - // This test therefore has 6 nodes, with 2 nodes having each type of - // skew. Then no majority (1/3 < 1/2) of nodes will agree on an - // actual close time. - - ConsensusParms const parms{}; - Sim sim; - - PeerGroup groupA = sim.createGroup(2); - PeerGroup const groupB = sim.createGroup(2); - PeerGroup const groupC = sim.createGroup(2); - PeerGroup network = groupA + groupB + groupC; - - network.trust(network); - network.connect(network, round(0.2 * parms.ledgerGRANULARITY)); - - // Run consensus without skew until we have a short close time - // resolution - Peer const* firstPeer = *groupA.begin(); - while (firstPeer->lastClosedLedger.closeTimeResolution() >= parms.proposeFRESHNESS) - sim.run(1); - - // Introduce a shift on the time of 2/3 of peers - for (Peer* peer : groupA) - peer->clockSkew = parms.proposeFRESHNESS / 2; - for (Peer* peer : groupB) - peer->clockSkew = parms.proposeFRESHNESS; - - sim.run(1); - - // All nodes agreed to disagree on the close time - if (BEAST_EXPECT(sim.synchronized())) - { - for (Peer const* peer : network) - BEAST_EXPECT(!peer->lastClosedLedger.closeAgree()); - } - } - - void - testWrongLCL() - { - using namespace csf; - using namespace std::chrono; - testcase("wrong LCL"); - - // Specialized test to exercise a temporary fork in which some peers - // are working on an incorrect prior ledger. - - ConsensusParms const parms{}; - - // Vary the time it takes to process validations to exercise detecting - // the wrong LCL at different phases of consensus - for (auto validationDelay : {0ms, parms.ledgerMinClose}) - { - // Consider 10 peers: - // 0 1 2 3 4 5 6 7 8 9 - // minority majorityA majorityB - // - // Nodes 0-1 trust nodes 0-4 - // Nodes 2-9 trust nodes 2-9 - // - // By submitting tx 0 to nodes 0-4 and tx 1 to nodes 5-9, - // nodes 0-1 will generate the wrong LCL (with tx 0). The remaining - // nodes will instead accept the ledger with tx 1. - - // Nodes 0-1 will detect this mismatch during a subsequent round - // since nodes 2-4 will validate a different ledger. - - // Nodes 0-1 will acquire the proper ledger from the network and - // resume consensus and eventually generate the dominant network - // ledger. - - // This topology can potentially fork with the above trust relations - // but that is intended for this test. - - Sim sim; - - PeerGroup minority = sim.createGroup(2); - PeerGroup const majorityA = sim.createGroup(3); - PeerGroup const majorityB = sim.createGroup(5); - - PeerGroup majority = majorityA + majorityB; - PeerGroup const network = minority + majority; - - SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); - minority.trustAndConnect(minority + majorityA, delay); - majority.trustAndConnect(majority, delay); - - CollectByNode jumps; - sim.collectors.add(jumps); - - BEAST_EXPECT(sim.trustGraph.canFork(parms.minConsensusPct / 100.)); - - // initial round to set prior state - sim.run(1); - - // Nodes in smaller UNL have seen tx 0, nodes in other unl have seen - // tx 1 - for (Peer* peer : network) - peer->delays.recvValidation = validationDelay; - for (Peer* peer : (minority + majorityA)) - peer->openTxs.insert(Tx{0}); - for (Peer* peer : majorityB) - peer->openTxs.insert(Tx{1}); - - // Run for additional rounds - // With no validation delay, only 2 more rounds are needed. - // 1. Round to generate different ledgers - // 2. Round to detect different prior ledgers (but still generate - // wrong ones) and recover within that round since wrong LCL - // is detected before we close - // - // With a validation delay of ledgerMIN_CLOSE, we need 3 more - // rounds. - // 1. Round to generate different ledgers - // 2. Round to detect different prior ledgers (but still generate - // wrong ones) but end up declaring consensus on wrong LCL (but - // with the right transaction set!). This is because we detect - // the wrong LCL after we have closed the ledger, so we declare - // consensus based solely on our peer proposals. But we haven't - // had time to acquire the right ledger. - // 3. Round to correct - sim.run(3); - - // The network never actually forks, since node 0-1 never see a - // quorum of validations to fully validate the incorrect chain. - - // However, for a non zero-validation delay, the network is not - // synchronized because nodes 0 and 1 are running one ledger behind - if (BEAST_EXPECT(sim.branches() == 1)) - { - for (Peer const* peer : majority) - { - // No jumps for majority nodes - BEAST_EXPECT(jumps[peer->id].closeJumps.empty()); - BEAST_EXPECT(jumps[peer->id].fullyValidatedJumps.empty()); - } - for (Peer const* peer : minority) - { - auto& peerJumps = jumps[peer->id]; - // last closed ledger jump between chains - { - if (BEAST_EXPECT(peerJumps.closeJumps.size() == 1)) - { - JumpCollector::Jump const& jump = peerJumps.closeJumps.front(); - // Jump is to a different chain - BEAST_EXPECT(jump.from.seq() <= jump.to.seq()); - BEAST_EXPECT(!jump.to.isAncestor(jump.from)); - } - } - // fully validated jump forward in same chain - { - if (BEAST_EXPECT(peerJumps.fullyValidatedJumps.size() == 1)) - { - JumpCollector::Jump const& jump = peerJumps.fullyValidatedJumps.front(); - // Jump is to a different chain with same seq - BEAST_EXPECT(jump.from.seq() < jump.to.seq()); - BEAST_EXPECT(jump.to.isAncestor(jump.from)); - } - } - } - } - } - - { - // Additional test engineered to switch LCL during the establish - // phase. This was added to trigger a scenario that previously - // crashed, in which switchLCL switched from establish to open - // phase, but still processed the establish phase logic. - - // Loner node will accept an initial ledger A, but all other nodes - // accept ledger B a bit later. By delaying the time it takes - // to process a validation, loner node will detect the wrongLCL - // after it is already in the establish phase of the next round. - - Sim sim; - PeerGroup loner = sim.createGroup(1); - PeerGroup const friends = sim.createGroup(3); - loner.trust(loner + friends); - - PeerGroup const others = sim.createGroup(6); - PeerGroup clique = friends + others; - clique.trust(clique); - - PeerGroup network = loner + clique; - network.connect(network, round(0.2 * parms.ledgerGRANULARITY)); - - // initial round to set prior state - sim.run(1); - for (Peer* peer : (loner + friends)) - peer->openTxs.insert(Tx(0)); - for (Peer* peer : others) - peer->openTxs.insert(Tx(1)); - - // Delay validation processing - for (Peer* peer : network) - peer->delays.recvValidation = parms.ledgerGRANULARITY; - - // additional rounds to generate wrongLCL and recover - sim.run(2); - - // Check all peers recovered - for (Peer const* p : network) - BEAST_EXPECT(p->prevLedgerID() == network[0]->prevLedgerID()); - } - } - - void - testConsensusCloseTimeRounding() - { - using namespace csf; - using namespace std::chrono; - testcase("consensus close time rounding"); - - // This is a specialized test engineered to yield ledgers with different - // close times even though the peers believe they had close time - // consensus on the ledger. - ConsensusParms const parms; - - Sim sim; - - // This requires a group of 4 fast and 2 slow peers to create a - // situation in which a subset of peers requires seeing additional - // proposals to declare consensus. - PeerGroup slow = sim.createGroup(2); - PeerGroup fast = sim.createGroup(4); - PeerGroup network = fast + slow; - - // Connected trust graph - network.trust(network); - - // Fast and slow network connections - fast.connect(fast, round(0.2 * parms.ledgerGRANULARITY)); - slow.connect(network, round(1.1 * parms.ledgerGRANULARITY)); - - // Run to the ledger *prior* to decreasing the resolution - sim.run(kIncreaseLedgerTimeResolutionEvery - 2); - - // In order to create the discrepancy, we want a case where if - // X = effCloseTime(closeTime, resolution, parentCloseTime) - // X != effCloseTime(X, resolution, parentCloseTime) - // - // That is, the effective close time is not a fixed point. This can - // happen if X = parentCloseTime + 1, but a subsequent rounding goes - // to the next highest multiple of resolution. - - // So we want to find an offset (now + offset) % 30s = 15 - // (now + offset) % 20s = 15 - // This way, the next ledger will close and round up Due to the - // network delay settings, the round of consensus will take 5s, so - // the next ledger's close time will - - NetClock::duration when = network[0]->now().time_since_epoch(); - - // Check we are before the 30s to 20s transition - NetClock::duration const resolution = network[0]->lastClosedLedger.closeTimeResolution(); - BEAST_EXPECT(resolution == NetClock::duration{30s}); - - while (((when % NetClock::duration{30s}) != NetClock::duration{15s}) || - ((when % NetClock::duration{20s}) != NetClock::duration{15s})) - when += 1s; - // Advance the clock without consensus running (IS THIS WHAT - // PREVENTS IT IN PRACTICE?) - sim.scheduler.stepFor(NetClock::time_point{when} - network[0]->now()); - - // Run one more ledger with 30s resolution - sim.run(1); - if (BEAST_EXPECT(sim.synchronized())) - { - // close time should be ahead of clock time since we engineered - // the close time to round up - for (Peer const* peer : network) - { - BEAST_EXPECT(peer->lastClosedLedger.closeTime() > peer->now()); - BEAST_EXPECT(peer->lastClosedLedger.closeAgree()); - } - } - - // All peers submit their own ID as a transaction - for (Peer* peer : network) - peer->submit(Tx{static_cast(peer->id)}); - - // Run 1 more round, this time it will have a decreased - // resolution of 20 seconds. - - // The network delays are engineered so that the slow peers - // initially have the wrong tx hash, but they see a majority - // of agreement from their peers and declare consensus - // - // The trick is that everyone starts with a raw close time of - // 84681s - // Which has - // effCloseTime(86481s, 20s, 86490s) = 86491s - // However, when the slow peers update their position, they change - // the close time to 86451s. The fast peers declare consensus with - // the 86481s as their position still. - // - // When accepted the ledger - // - fast peers use eff(86481s) -> 86491s as the close time - // - slow peers use eff(eff(86481s)) -> eff(86491s) -> 86500s! - - sim.run(1); - - BEAST_EXPECT(sim.synchronized()); - } - - void - testFork() - { - using namespace csf; - using namespace std::chrono; - testcase("fork"); - - std::uint32_t const numPeers = 10; - // Vary overlap between two UNLs - for (std::uint32_t overlap = 0; overlap <= numPeers; ++overlap) - { - ConsensusParms const parms{}; - Sim sim; - - std::uint32_t const numA = (numPeers - overlap) / 2; - std::uint32_t const numB = numPeers - numA - overlap; - - PeerGroup const aOnly = sim.createGroup(numA); - PeerGroup const bOnly = sim.createGroup(numB); - PeerGroup const commonOnly = sim.createGroup(overlap); - - PeerGroup a = aOnly + commonOnly; - PeerGroup b = bOnly + commonOnly; - - PeerGroup const network = a + b; - - SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); - a.trustAndConnect(a, delay); - b.trustAndConnect(b, delay); - - // Initial round to set prior state - sim.run(1); - for (Peer* peer : network) - { - // Nodes have only seen transactions from their neighbors - peer->openTxs.insert(Tx{static_cast(peer->id)}); - for (Peer const* to : sim.trustGraph.trustedPeers(peer)) - peer->openTxs.insert(Tx{static_cast(to->id)}); - } - sim.run(1); - - // Fork should not happen for 40% or greater overlap - // Since the overlapped nodes have a UNL that is the union of the - // two cliques, the maximum sized UNL list is the number of peers - if (overlap > 0.4 * numPeers) - { - BEAST_EXPECT(sim.synchronized()); - } - else - { - // Even if we do fork, there shouldn't be more than 3 ledgers - // One for cliqueA, one for cliqueB and one for nodes in both - BEAST_EXPECT(sim.branches() <= 3); - } - } - } - - void - testHubNetwork() - { - using namespace csf; - using namespace std::chrono; - testcase("hub network"); - - // Simulate a set of 5 validators that aren't directly connected but - // rely on a single hub node for communication - - ConsensusParms const parms{}; - Sim sim; - PeerGroup validators = sim.createGroup(5); - PeerGroup center = sim.createGroup(1); - validators.trust(validators); - center.trust(validators); - - SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); - validators.connect(center, delay); - - center[0]->runAsValidator = false; - - // prep round to set initial state. - sim.run(1); - - // everyone submits their own ID as a TX and relay it to peers - for (Peer* p : validators) - p->submit(Tx(static_cast(p->id))); - - sim.run(1); - - // All peers are in sync - BEAST_EXPECT(sim.synchronized()); - } - - // Helper collector for testPreferredByBranch - // Invasively disconnects network at bad times to cause splits - struct Disruptor - { - csf::PeerGroup& network; - csf::PeerGroup& groupCfast; - csf::PeerGroup& groupCsplit; - csf::SimDuration delay; - bool reconnected = false; - - Disruptor(csf::PeerGroup& net, csf::PeerGroup& c, csf::PeerGroup& split, csf::SimDuration d) - : network(net), groupCfast(c), groupCsplit(split), delay(d) - { - } - - template - void - on(csf::PeerID, csf::SimTime, E const&) - { - } - - void - on(csf::PeerID who, csf::SimTime, csf::FullyValidateLedger const& e) - { - using namespace std::chrono; - // As soon as the fastC node fully validates C, disconnect - // ALL c nodes from the network. The fast C node needs to disconnect - // as well to prevent it from relaying the validations it did see - if (who == groupCfast[0]->id && e.ledger.seq() == csf::Ledger::Seq{2}) - { - network.disconnect(groupCsplit); - network.disconnect(groupCfast); - } - } - - void - on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e) - { - // As soon as anyone generates a child of B or C, reconnect the - // network so those validations make it through - if (!reconnected && e.ledger.seq() == csf::Ledger::Seq{3}) - { - reconnected = true; - network.connect(groupCsplit, delay); - } - } - }; - - void - testPreferredByBranch() - { - using namespace csf; - using namespace std::chrono; - testcase("preferred by branch"); - - // Simulate network splits that are prevented from forking when using - // preferred ledger by trie. This is a contrived example that involves - // excessive network splits, but demonstrates the safety improvement - // from the preferred ledger by trie approach. - - // Consider 10 validating nodes that comprise a single common UNL - // Ledger history: - // 1: A - // _/ \_ - // 2: B C - // _/ _/ \_ - // 3: D C' |||||||| (8 different ledgers) - - // - All nodes generate the common ledger A - // - 2 nodes generate B and 8 nodes generate C - // - Only 1 of the C nodes sees all the C validations and fully - // validates C. The rest of the C nodes split at just the right time - // such that they never see any C validations but their own. - // - The C nodes continue and generate 8 different child ledgers. - // - Meanwhile, the D nodes only saw 1 validation for C and 2 - // validations - // for B. - // - The network reconnects and the validations for generation 3 ledgers - // are observed (D and the 8 C's) - // - In the old approach, 2 votes for D outweighs 1 vote for each C' - // so the network would avalanche towards D and fully validate it - // EVEN though C was fully validated by one node - // - In the new approach, 2 votes for D are not enough to outweight the - // 8 implicit votes for C, so nodes will avalanche to C instead - - ConsensusParms const parms{}; - Sim sim; - - // Goes A->B->D - PeerGroup const groupABD = sim.createGroup(2); - // Single node that initially fully validates C before the split - PeerGroup groupCfast = sim.createGroup(1); - // Generates C, but fails to fully validate before the split - PeerGroup groupCsplit = sim.createGroup(7); - - PeerGroup groupNotFastC = groupABD + groupCsplit; - PeerGroup network = groupABD + groupCsplit + groupCfast; - - SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); - SimDuration const fDelay = round(0.1 * parms.ledgerGRANULARITY); - - network.trust(network); - // C must have a shorter delay to see all the validations before the - // other nodes - network.connect(groupCfast, fDelay); - // The rest of the network is connected at the same speed - groupNotFastC.connect(groupNotFastC, delay); - - Disruptor dc(network, groupCfast, groupCsplit, delay); - sim.collectors.add(dc); - - // Consensus round to generate ledger A - sim.run(1); - BEAST_EXPECT(sim.synchronized()); - - // Next round generates B and C - // To force B, we inject an extra transaction in to those nodes - for (Peer* peer : groupABD) - { - peer->txInjections.emplace(peer->lastClosedLedger.seq(), Tx{42}); - } - // The Disruptor will ensure that nodes disconnect before the C - // validations make it to all but the fastC node - sim.run(1); - - // We are no longer in sync, but have not yet forked: - // 9 nodes consider A the last fully validated ledger and fastC sees C - BEAST_EXPECT(!sim.synchronized()); - BEAST_EXPECT(sim.branches() == 1); - - // Run another round to generate the 8 different C' ledgers - for (Peer* p : network) - p->submit(Tx(static_cast(p->id))); - sim.run(1); - - // Still not forked - BEAST_EXPECT(!sim.synchronized()); - BEAST_EXPECT(sim.branches() == 1); - - // Disruptor will reconnect all but the fastC node - sim.run(1); - - if (BEAST_EXPECT(sim.branches() == 1)) - { - BEAST_EXPECT(sim.synchronized()); - } - else // old approach caused a fork - { - BEAST_EXPECT(sim.branches(groupNotFastC) == 1); - BEAST_EXPECT(sim.synchronized(groupNotFastC) == 1); - } - } - - // Helper collector for testPauseForLaggards - // This will remove the ledgerAccept delay used to - // initially create the slow vs. fast validator groups. - struct UndoDelay - { - csf::PeerGroup& g; - - UndoDelay(csf::PeerGroup& a) : g(a) - { - } - - template - void - on(csf::PeerID, csf::SimTime, E const&) - { - } - - void - on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e) - { - for (csf::Peer* p : g) - { - if (p->id == who) - p->delays.ledgerAccept = std::chrono::seconds{0}; - } - } - }; - - void - testPauseForLaggards() - { - using namespace csf; - using namespace std::chrono; - testcase("pause for laggards"); - - // Test that validators that jump ahead of the network slow - // down. - - // We engineer the following validated ledger history scenario: - // - // / --> B1 --> C1 --> ... -> G1 "ahead" - // A - // \ --> B2 --> C2 "behind" - // - // After validating a common ledger A, a set of "behind" validators - // briefly run slower and validate the lower chain of ledgers. - // The "ahead" validators run normal speed and run ahead validating the - // upper chain of ledgers. - // - // Due to the uncommitted support definition of the preferred branch - // protocol, even if the "behind" validators are a majority, the "ahead" - // validators cannot jump to the proper branch until the "behind" - // validators catch up to the same sequence number. For this test to - // succeed, the ahead validators need to briefly slow down consensus. - - ConsensusParms const parms{}; - Sim sim; - SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); - - PeerGroup behind = sim.createGroup(3); - PeerGroup const ahead = sim.createGroup(2); - PeerGroup network = ahead + behind; - - hash_set trustedKeys; - for (Peer const* p : network) - trustedKeys.insert(p->key); - for (Peer* p : network) - p->trustedKeys = trustedKeys; - - network.trustAndConnect(network, delay); - - // Initial seed round to set prior state - sim.run(1); - - // Have the "behind" group initially take a really long time to - // accept a ledger after ending deliberation - for (Peer* p : behind) - p->delays.ledgerAccept = 20s; - - // Use the collector to revert the delay after the single - // slow ledger is generated - UndoDelay undoDelay{behind}; - sim.collectors.add(undoDelay); - // Run the simulation for 100 seconds of simulation time with - std::chrono::nanoseconds const simDuration = 100s; - - // Simulate clients submitting 1 tx every 5 seconds to a random - // validator - Rate const rate{.count = 1, .duration = 5s}; - auto peerSelector = makeSelector( - network.begin(), network.end(), std::vector(network.size(), 1.), sim.rng); - auto txSubmitter = makeSubmitter( - ConstantDistribution{rate.inv()}, - sim.scheduler.now(), - sim.scheduler.now() + simDuration, - peerSelector, - sim.scheduler, - sim.rng); - - // Run simulation - sim.run(simDuration); - - // Verify that the network recovered - BEAST_EXPECT(sim.synchronized()); - } - - void - testDisputes() - { - testcase("disputes"); - - using namespace csf; - - // Test dispute objects directly - using Dispute = DisputedTx; - - Tx const txTrue{99}; - Tx const txFalse{98}; - Tx const txFollowingTrue{97}; - Tx const txFollowingFalse{96}; - int const numPeers = 100; - ConsensusParms const p; - std::size_t peersUnchanged = 0; - - auto logs = std::make_unique(beast::Severity::Error); - auto j = logs->journal("Test"); - auto clog = std::make_unique(); - - // Three cases: - // 1 proposing, initial vote yes - // 2 proposing, initial vote no - // 3 not proposing, initial vote doesn't matter after the first update, - // use yes - { - Dispute proposingTrue{txTrue.id(), true, numPeers, journal_}; - Dispute proposingFalse{txFalse.id(), false, numPeers, journal_}; - Dispute followingTrue{txFollowingTrue.id(), true, numPeers, journal_}; - Dispute followingFalse{txFollowingFalse.id(), false, numPeers, journal_}; - BEAST_EXPECT(proposingTrue.id() == 99); - BEAST_EXPECT(proposingFalse.id() == 98); - BEAST_EXPECT(followingTrue.id() == 97); - BEAST_EXPECT(followingFalse.id() == 96); - - // Create an even split in the peer votes - for (int i = 0; i < numPeers; ++i) - { - BEAST_EXPECT(proposingTrue.setVote(PeerID(i), i < 50)); - BEAST_EXPECT(proposingFalse.setVote(PeerID(i), i < 50)); - BEAST_EXPECT(followingTrue.setVote(PeerID(i), i < 50)); - BEAST_EXPECT(followingFalse.setVote(PeerID(i), i < 50)); - } - // Switch the middle vote to match mine - BEAST_EXPECT(proposingTrue.setVote(PeerID(50), true)); - BEAST_EXPECT(proposingFalse.setVote(PeerID(49), false)); - BEAST_EXPECT(followingTrue.setVote(PeerID(50), true)); - BEAST_EXPECT(followingFalse.setVote(PeerID(49), false)); - - // no changes yet - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(clog->str().empty()); - - // I'm in the majority, my vote should not change - BEAST_EXPECT(!proposingTrue.updateVote(5, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(5, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(5, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(5, false, p)); - - BEAST_EXPECT(!proposingTrue.updateVote(10, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(10, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(10, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(10, false, p)); - - peersUnchanged = 2; - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(clog->str().empty()); - - // Right now, the vote is 51%. The requirement is about to jump to - // 65% - BEAST_EXPECT(proposingTrue.updateVote(55, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(55, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(55, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(55, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == false); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - // 16 validators change their vote to match my original vote - for (int i = 0; i < 16; ++i) - { - auto pTrue = PeerID(numPeers - i - 1); - auto pFalse = PeerID(i); - BEAST_EXPECT(proposingTrue.setVote(pTrue, true)); - BEAST_EXPECT(proposingFalse.setVote(pFalse, false)); - BEAST_EXPECT(followingTrue.setVote(pTrue, true)); - BEAST_EXPECT(followingFalse.setVote(pFalse, false)); - } - // The vote should now be 66%, threshold is 65% - BEAST_EXPECT(proposingTrue.updateVote(60, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(60, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(60, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(60, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - // Threshold jumps to 70% - BEAST_EXPECT(proposingTrue.updateVote(86, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(86, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(86, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(86, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == false); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - // 5 more validators change their vote to match my original vote - for (int i = 16; i < 21; ++i) - { - auto pTrue = PeerID(numPeers - i - 1); - auto pFalse = PeerID(i); - BEAST_EXPECT(proposingTrue.setVote(pTrue, true)); - BEAST_EXPECT(proposingFalse.setVote(pFalse, false)); - BEAST_EXPECT(followingTrue.setVote(pTrue, true)); - BEAST_EXPECT(followingFalse.setVote(pFalse, false)); - } - - // The vote should now be 71%, threshold is 70% - BEAST_EXPECT(proposingTrue.updateVote(90, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(90, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(90, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(90, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - // The vote should now be 71%, threshold is 70% - BEAST_EXPECT(!proposingTrue.updateVote(150, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(150, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(150, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(150, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - // The vote should now be 71%, threshold is 70% - BEAST_EXPECT(!proposingTrue.updateVote(190, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(190, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(190, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(190, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - peersUnchanged = 3; - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(clog->str().empty()); - - // Threshold jumps to 95% - BEAST_EXPECT(proposingTrue.updateVote(220, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(220, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(220, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(220, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == false); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - // 25 more validators change their vote to match my original vote - for (int i = 21; i < 46; ++i) - { - auto pTrue = PeerID(numPeers - i - 1); - auto pFalse = PeerID(i); - BEAST_EXPECT(proposingTrue.setVote(pTrue, true)); - BEAST_EXPECT(proposingFalse.setVote(pFalse, false)); - BEAST_EXPECT(followingTrue.setVote(pTrue, true)); - BEAST_EXPECT(followingFalse.setVote(pFalse, false)); - } - - // The vote should now be 96%, threshold is 95% - BEAST_EXPECT(proposingTrue.updateVote(250, true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(250, true, p)); - BEAST_EXPECT(!followingTrue.updateVote(250, false, p)); - BEAST_EXPECT(!followingFalse.updateVote(250, false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - for (peersUnchanged = 0; peersUnchanged < 6; ++peersUnchanged) - { - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged, j, clog)); - BEAST_EXPECT(clog->str().empty()); - } - - auto expectStalled = [this, &clog]( - int txid, - bool ourVote, - int ourTime, - int peerTime, - int support, - std::uint32_t line) { - using namespace std::string_literals; - - auto const s = clog->str(); - expect(s.find("stalled"), s, __FILE__, line); - expect(s.starts_with("Transaction "s + std::to_string(txid)), s, __FILE__, line); - expect(s.contains("voting "s + (ourVote ? "YES" : "NO")), s, __FILE__, line); - expect( - s.contains("for "s + std::to_string(ourTime) + " rounds."s), s, __FILE__, line); - expect( - s.contains("votes in "s + std::to_string(peerTime) + " rounds."), - s, - __FILE__, - line); - expect( - s.ends_with("has "s + std::to_string(support) + "% support. "s), - s, - __FILE__, - line); - clog = std::make_unique(); - }; - - for (int i = 0; i < 1; ++i) - { - BEAST_EXPECT(!proposingTrue.updateVote(250 + (10 * i), true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(250 + (10 * i), true, p)); - BEAST_EXPECT(!followingTrue.updateVote(250 + (10 * i), false, p)); - BEAST_EXPECT(!followingFalse.updateVote(250 + (10 * i), false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - // true vote has changed recently, so not stalled - BEAST_EXPECT(!proposingTrue.stalled(p, true, 0, j, clog)); - BEAST_EXPECT(clog->str().empty()); - // remaining votes have been unchanged in so long that we only - // need to hit the second round at 95% to be stalled, regardless - // of peers - BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog)); - expectStalled(98, false, 11, 0, 2, __LINE__); - BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog)); - expectStalled(97, true, 11, 0, 97, __LINE__); - BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog)); - expectStalled(96, false, 11, 0, 3, __LINE__); - - // true vote has changed recently, so not stalled - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged, j, clog)); - BEAST_EXPECTS(clog->str().empty(), clog->str()); - // remaining votes have been unchanged in so long that we only - // need to hit the second round at 95% to be stalled, regardless - // of peers - BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged, j, clog)); - expectStalled(98, false, 11, 6, 2, __LINE__); - BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged, j, clog)); - expectStalled(97, true, 11, 6, 97, __LINE__); - BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged, j, clog)); - expectStalled(96, false, 11, 6, 3, __LINE__); - } - for (int i = 1; i < 3; ++i) - { - BEAST_EXPECT(!proposingTrue.updateVote(250 + (10 * i), true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(250 + (10 * i), true, p)); - BEAST_EXPECT(!followingTrue.updateVote(250 + (10 * i), false, p)); - BEAST_EXPECT(!followingFalse.updateVote(250 + (10 * i), false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - // true vote changed 2 rounds ago, and peers are changing, so - // not stalled - BEAST_EXPECT(!proposingTrue.stalled(p, true, 0, j, clog)); - BEAST_EXPECTS(clog->str().empty(), clog->str()); - // still stalled - BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog)); - expectStalled(98, false, 11 + i, 0, 2, __LINE__); - BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog)); - expectStalled(97, true, 11 + i, 0, 97, __LINE__); - BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog)); - expectStalled(96, false, 11 + i, 0, 3, __LINE__); - - // true vote changed 2 rounds ago, and peers are NOT changing, - // so stalled - BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged, j, clog)); - expectStalled(99, true, 1 + i, 6, 97, __LINE__); - // still stalled - BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged, j, clog)); - expectStalled(98, false, 11 + i, 6, 2, __LINE__); - BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged, j, clog)); - expectStalled(97, true, 11 + i, 6, 97, __LINE__); - BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged, j, clog)); - expectStalled(96, false, 11 + i, 6, 3, __LINE__); - } - for (int i = 3; i < 5; ++i) - { - BEAST_EXPECT(!proposingTrue.updateVote(250 + (10 * i), true, p)); - BEAST_EXPECT(!proposingFalse.updateVote(250 + (10 * i), true, p)); - BEAST_EXPECT(!followingTrue.updateVote(250 + (10 * i), false, p)); - BEAST_EXPECT(!followingFalse.updateVote(250 + (10 * i), false, p)); - - BEAST_EXPECT(proposingTrue.getOurVote() == true); - BEAST_EXPECT(proposingFalse.getOurVote() == false); - BEAST_EXPECT(followingTrue.getOurVote() == true); - BEAST_EXPECT(followingFalse.getOurVote() == false); - - BEAST_EXPECT(proposingTrue.stalled(p, true, 0, j, clog)); - expectStalled(99, true, 1 + i, 0, 97, __LINE__); - BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog)); - expectStalled(98, false, 11 + i, 0, 2, __LINE__); - BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog)); - expectStalled(97, true, 11 + i, 0, 97, __LINE__); - BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog)); - expectStalled(96, false, 11 + i, 0, 3, __LINE__); - - BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged, j, clog)); - expectStalled(99, true, 1 + i, 6, 97, __LINE__); - BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged, j, clog)); - expectStalled(98, false, 11 + i, 6, 2, __LINE__); - BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged, j, clog)); - expectStalled(97, true, 11 + i, 6, 97, __LINE__); - BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged, j, clog)); - expectStalled(96, false, 11 + i, 6, 3, __LINE__); - } - } - } - - void - run() override - { - testShouldCloseLedger(); - testCheckConsensus(); - - testStandalone(); - testPeersAgree(); - testSlowPeers(); - testCloseTimeDisagree(); - testWrongLCL(); - testConsensusCloseTimeRounding(); - testFork(); - testHubNetwork(); - testPreferredByBranch(); - testPauseForLaggards(); - testDisputes(); - } -}; - -BEAST_DEFINE_TESTSUITE(Consensus, consensus, xrpl); -} // namespace xrpl::test diff --git a/src/test/consensus/DistributedValidatorsSim_test.cpp b/src/test/consensus/DistributedValidatorsSim_test.cpp deleted file mode 100644 index 437ad81ee0..0000000000 --- a/src/test/consensus/DistributedValidatorsSim_test.cpp +++ /dev/null @@ -1,253 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl::test { - -/** - * In progress simulations for diversifying and distributing validators - */ -class DistributedValidators_test : public beast::unit_test::Suite -{ - void - completeTrustCompleteConnectFixedDelay( - std::size_t numPeers, - std::chrono::milliseconds delay = std::chrono::milliseconds(200), - bool printHeaders = false) - { - using namespace csf; - using namespace std::chrono; - - // Initialize persistent collector logs specific to this method - std::string const prefix = - "DistributedValidators_" - "completeTrustCompleteConnectFixedDelay"; - std::fstream txLog(prefix + "_tx.csv", std::ofstream::app), - ledgerLog(prefix + "_ledger.csv", std::ofstream::app); - - // title - log << prefix << "(" << numPeers << "," << delay.count() << ")" << std::endl; - - // number of peers, UNLs, connections - BEAST_EXPECT(numPeers >= 1); - - Sim sim; - PeerGroup peers = sim.createGroup(numPeers); - - // complete trust graph - peers.trust(peers); - - // complete connect graph with fixed delay - peers.connect(peers, delay); - - // Initialize collectors to track statistics to report - TxCollector txCollector; - LedgerCollector ledgerCollector; - auto colls = makeCollectors(txCollector, ledgerCollector); - sim.collectors.add(colls); - - // Initial round to set prior state - sim.run(1); - - // Run for 10 minutes, submitting 100 tx/second - std::chrono::nanoseconds const simDuration = 10min; - std::chrono::nanoseconds const quiet = 10s; - Rate const rate{.count = 100, .duration = 1000ms}; - - // Initialize timers - HeartbeatTimer heart(sim.scheduler); - - // txs, start/stop/step, target - auto peerSelector = - makeSelector(peers.begin(), peers.end(), std::vector(numPeers, 1.), sim.rng); - auto txSubmitter = makeSubmitter( - ConstantDistribution{rate.inv()}, - sim.scheduler.now() + quiet, - sim.scheduler.now() + simDuration - quiet, - peerSelector, - sim.scheduler, - sim.rng); - - // run simulation for given duration - heart.start(); - sim.run(simDuration); - - // BEAST_EXPECT(sim.branches() == 1); - // BEAST_EXPECT(sim.synchronized()); - - log << std::right; - log << "| Peers: " << std::setw(2) << peers.size(); - log << " | Duration: " << std::setw(6) << duration_cast(simDuration).count() - << " ms"; - log << " | Branches: " << std::setw(1) << sim.branches(); - log << " | Synchronized: " << std::setw(1) << (sim.synchronized() ? "Y" : "N"); - log << " |" << std::endl; - - txCollector.report(simDuration, log, true); - ledgerCollector.report(simDuration, log, false); - - std::string const tag = std::to_string(numPeers); - txCollector.csv(simDuration, txLog, tag, printHeaders); - ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders); - - log << std::endl; - } - - void - completeTrustScaleFreeConnectFixedDelay( - std::size_t numPeers, - std::chrono::milliseconds delay = std::chrono::milliseconds(200), - bool printHeaders = false) - { - using namespace csf; - using namespace std::chrono; - - // Initialize persistent collector logs specific to this method - std::string const prefix = - "DistributedValidators__" - "completeTrustScaleFreeConnectFixedDelay"; - std::fstream txLog(prefix + "_tx.csv", std::ofstream::app), - ledgerLog(prefix + "_ledger.csv", std::ofstream::app); - - // title - log << prefix << "(" << numPeers << "," << delay.count() << ")" << std::endl; - - // number of peers, UNLs, connections - int const numCNLs = std::max(int(1.00 * numPeers), 1); - int const minCNLSize = std::max(int(0.25 * numCNLs), 1); - int const maxCNLSize = std::max(int(0.50 * numCNLs), 1); - BEAST_EXPECT(numPeers >= 1); - BEAST_EXPECT(numCNLs >= 1); - BEAST_EXPECT(1 <= minCNLSize && minCNLSize <= maxCNLSize && maxCNLSize <= numPeers); - - Sim sim; - PeerGroup peers = sim.createGroup(numPeers); - - // complete trust graph - peers.trust(peers); - - // scale-free connect graph with fixed delay - std::vector const ranks = sample(peers.size(), PowerLawDistribution{1, 3}, sim.rng); - randomRankedConnect( - peers, - ranks, - numCNLs, - std::uniform_int_distribution<>{minCNLSize, maxCNLSize}, - sim.rng, - delay); - - // Initialize collectors to track statistics to report - TxCollector txCollector; - LedgerCollector ledgerCollector; - auto colls = makeCollectors(txCollector, ledgerCollector); - sim.collectors.add(colls); - - // Initial round to set prior state - sim.run(1); - - // Run for 10 minutes, submitting 100 tx/second - std::chrono::nanoseconds const simDuration = 10min; - std::chrono::nanoseconds const quiet = 10s; - Rate const rate{.count = 100, .duration = 1000ms}; - - // Initialize timers - HeartbeatTimer heart(sim.scheduler); - - // txs, start/stop/step, target - auto peerSelector = - makeSelector(peers.begin(), peers.end(), std::vector(numPeers, 1.), sim.rng); - auto txSubmitter = makeSubmitter( - ConstantDistribution{rate.inv()}, - sim.scheduler.now() + quiet, - sim.scheduler.now() + simDuration - quiet, - peerSelector, - sim.scheduler, - sim.rng); - - // run simulation for given duration - heart.start(); - sim.run(simDuration); - - // BEAST_EXPECT(sim.branches() == 1); - // BEAST_EXPECT(sim.synchronized()); - - log << std::right; - log << "| Peers: " << std::setw(2) << peers.size(); - log << " | Duration: " << std::setw(6) << duration_cast(simDuration).count() - << " ms"; - log << " | Branches: " << std::setw(1) << sim.branches(); - log << " | Synchronized: " << std::setw(1) << (sim.synchronized() ? "Y" : "N"); - log << " |" << std::endl; - - txCollector.report(simDuration, log, true); - ledgerCollector.report(simDuration, log, false); - - std::string const tag = std::to_string(numPeers); - txCollector.csv(simDuration, txLog, tag, printHeaders); - ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders); - - log << std::endl; - } - - void - run() override - { - std::string const defaultArgs = "5 200"; - std::string const args = arg().empty() ? defaultArgs : arg(); - std::stringstream argStream(args); - - int maxNumValidators = 0; - int delayCount(200); - argStream >> maxNumValidators; - argStream >> delayCount; - - std::chrono::milliseconds const delay(delayCount); - - log << "DistributedValidators: 1 to " << maxNumValidators << " Peers" << std::endl; - - /** - * Simulate with N = 1 to N - * - complete trust graph is complete - * - complete network connectivity - * - fixed delay for network links - */ - completeTrustCompleteConnectFixedDelay(1, delay, true); - for (int i = 2; i <= maxNumValidators; i++) - { - completeTrustCompleteConnectFixedDelay(i, delay); - } - - /** - * Simulate with N = 1 to N - * - complete trust graph is complete - * - scale-free network connectivity - * - fixed delay for network links - */ - completeTrustScaleFreeConnectFixedDelay(1, delay, true); - for (int i = 2; i <= maxNumValidators; i++) - { - completeTrustScaleFreeConnectFixedDelay(i, delay); - } - } -}; - -BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(DistributedValidators, consensus, xrpl, 2); - -} // namespace xrpl::test diff --git a/src/test/consensus/LedgerTiming_test.cpp b/src/test/consensus/LedgerTiming_test.cpp deleted file mode 100644 index 632361c799..0000000000 --- a/src/test/consensus/LedgerTiming_test.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include -#include - -#include -#include -#include - -namespace xrpl::test { - -class LedgerTiming_test : public beast::unit_test::Suite -{ - void - testGetNextLedgerTimeResolution() - { - // helper to iteratively call into getNextLedgerTimeResolution - struct TestRes - { - std::uint32_t decrease = 0; - std::uint32_t equal = 0; - std::uint32_t increase = 0; - - static TestRes - run(bool previousAgree, std::uint32_t rounds) - { - TestRes res; - auto closeResolution = kLedgerDefaultTimeResolution; - auto nextCloseResolution = closeResolution; - std::uint32_t round = 0; - do - { - nextCloseResolution = - getNextLedgerTimeResolution(closeResolution, previousAgree, ++round); - if (nextCloseResolution < closeResolution) - { - ++res.decrease; - } - else if (nextCloseResolution > closeResolution) - { - ++res.increase; - } - else - { - ++res.equal; - } - std::swap(nextCloseResolution, closeResolution); - } while (round < rounds); - return res; - } - }; - - // If we never agree on close time, only can increase resolution - // until hit the max - auto decreases = TestRes::run(false, 10); - BEAST_EXPECT(decreases.increase == 3); - BEAST_EXPECT(decreases.decrease == 0); - BEAST_EXPECT(decreases.equal == 7); - - // If we always agree on close time, only can decrease resolution - // until hit the min - auto increases = TestRes::run(false, 100); - BEAST_EXPECT(increases.increase == 3); - BEAST_EXPECT(increases.decrease == 0); - BEAST_EXPECT(increases.equal == 97); - } - - void - testRoundCloseTime() - { - using namespace std::chrono_literals; - // A closeTime equal to the epoch is not modified - using tp = NetClock::time_point; - tp const def; - BEAST_EXPECT(def == roundCloseTime(def, 30s)); - - // Otherwise, the closeTime is rounded to the nearest - // rounding up on ties - BEAST_EXPECT(tp{0s} == roundCloseTime(tp{29s}, 60s)); - BEAST_EXPECT(tp{30s} == roundCloseTime(tp{30s}, 1s)); - BEAST_EXPECT(tp{60s} == roundCloseTime(tp{31s}, 60s)); - BEAST_EXPECT(tp{60s} == roundCloseTime(tp{30s}, 60s)); - BEAST_EXPECT(tp{60s} == roundCloseTime(tp{59s}, 60s)); - BEAST_EXPECT(tp{60s} == roundCloseTime(tp{60s}, 60s)); - BEAST_EXPECT(tp{60s} == roundCloseTime(tp{61s}, 60s)); - } - - void - testEffCloseTime() - { - using namespace std::chrono_literals; - using tp = NetClock::time_point; - tp close = effCloseTime(tp{10s}, 30s, tp{0s}); - BEAST_EXPECT(close == tp{1s}); - - close = effCloseTime(tp{16s}, 30s, tp{0s}); - BEAST_EXPECT(close == tp{30s}); - - close = effCloseTime(tp{16s}, 30s, tp{30s}); - BEAST_EXPECT(close == tp{31s}); - - close = effCloseTime(tp{16s}, 30s, tp{60s}); - BEAST_EXPECT(close == tp{61s}); - - close = effCloseTime(tp{31s}, 30s, tp{0s}); - BEAST_EXPECT(close == tp{30s}); - } - - void - run() override - { - testGetNextLedgerTimeResolution(); - testRoundCloseTime(); - testEffCloseTime(); - } -}; - -BEAST_DEFINE_TESTSUITE(LedgerTiming, consensus, xrpl); -} // namespace xrpl::test diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp deleted file mode 100644 index a4eb7bc087..0000000000 --- a/src/test/consensus/LedgerTrie_test.cpp +++ /dev/null @@ -1,716 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -namespace xrpl::test { - -class LedgerTrie_test : public beast::unit_test::Suite -{ - void - testInsert() - { - using namespace csf; - // Single entry by itself - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); - - t.insert(h["abc"]); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - } - // Suffix of existing (extending tree) - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - BEAST_EXPECT(t.checkInvariants()); - // extend with no siblings - t.insert(h["abcd"]); - BEAST_EXPECT(t.checkInvariants()); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); - - // extend with existing sibling - t.insert(h["abce"]); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); - } - // uncommitted of existing node - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abcd"]); - BEAST_EXPECT(t.checkInvariants()); - // uncommitted with no siblings - t.insert(h["abcdf"]); - BEAST_EXPECT(t.checkInvariants()); - - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2); - BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1); - - // uncommitted with existing child - t.insert(h["abc"]); - BEAST_EXPECT(t.checkInvariants()); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2); - BEAST_EXPECT(t.tipSupport(h["abcdf"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcdf"]) == 1); - } - // Suffix + uncommitted of existing node - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abcd"]); - BEAST_EXPECT(t.checkInvariants()); - t.insert(h["abce"]); - BEAST_EXPECT(t.checkInvariants()); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abce"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abce"]) == 1); - } - // Suffix + uncommitted with existing child - { - // abcd : abcde, abcf - - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abcd"]); - BEAST_EXPECT(t.checkInvariants()); - t.insert(h["abcde"]); - BEAST_EXPECT(t.checkInvariants()); - t.insert(h["abcf"]); - BEAST_EXPECT(t.checkInvariants()); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 2); - BEAST_EXPECT(t.tipSupport(h["abcf"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcf"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abcde"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcde"]) == 1); - } - - // Multiple counts - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["ab"], 4); - BEAST_EXPECT(t.tipSupport(h["ab"]) == 4); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 4); - BEAST_EXPECT(t.tipSupport(h["a"]) == 0); - BEAST_EXPECT(t.branchSupport(h["a"]) == 4); - - t.insert(h["abc"], 2); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - BEAST_EXPECT(t.tipSupport(h["ab"]) == 4); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 6); - BEAST_EXPECT(t.tipSupport(h["a"]) == 0); - BEAST_EXPECT(t.branchSupport(h["a"]) == 6); - } - } - - void - testRemove() - { - using namespace csf; - // Not in trie - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - - BEAST_EXPECT(!t.remove(h["ab"])); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(!t.remove(h["a"])); - BEAST_EXPECT(t.checkInvariants()); - } - // In trie but with 0 tip support - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abcd"]); - t.insert(h["abce"]); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - BEAST_EXPECT(!t.remove(h["abc"])); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - } - // In trie with > 1 tip support - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"], 2); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); - BEAST_EXPECT(t.remove(h["abc"])); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - - t.insert(h["abc"], 1); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 2); - BEAST_EXPECT(t.remove(h["abc"], 2)); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - - t.insert(h["abc"], 3); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 3); - BEAST_EXPECT(t.remove(h["abc"], 300)); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - } - // In trie with = 1 tip support, no children - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["ab"]); - t.insert(h["abc"]); - - BEAST_EXPECT(t.tipSupport(h["ab"]) == 1); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); - - BEAST_EXPECT(t.remove(h["abc"])); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["ab"]) == 1); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 0); - } - // In trie with = 1 tip support, 1 child - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["ab"]); - t.insert(h["abc"]); - t.insert(h["abcd"]); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); - - BEAST_EXPECT(t.remove(h["abc"])); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 1); - } - // In trie with = 1 tip support, > 1 children - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["ab"]); - t.insert(h["abc"]); - t.insert(h["abcd"]); - t.insert(h["abce"]); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 3); - - BEAST_EXPECT(t.remove(h["abc"])); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 2); - } - - // In trie with = 1 tip support, parent compaction - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["ab"]); - t.insert(h["abc"]); - t.insert(h["abd"]); - BEAST_EXPECT(t.checkInvariants()); - t.remove(h["ab"]); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abd"]) == 1); - BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); - - t.remove(h["abd"]); - BEAST_EXPECT(t.checkInvariants()); - - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); - } - } - - void - testEmpty() - { - using namespace csf; - LedgerTrie t; - LedgerHistoryHelper h; - BEAST_EXPECT(t.empty()); - - Ledger const genesis = h[""]; - t.insert(genesis); - BEAST_EXPECT(!t.empty()); - t.remove(genesis); - BEAST_EXPECT(t.empty()); - - t.insert(h["abc"]); - BEAST_EXPECT(!t.empty()); - t.remove(h["abc"]); - BEAST_EXPECT(t.empty()); - } - - void - testSupport() - { - using namespace csf; - - LedgerTrie t; - LedgerHistoryHelper h; - BEAST_EXPECT(t.tipSupport(h["a"]) == 0); - BEAST_EXPECT(t.tipSupport(h["axy"]) == 0); - - BEAST_EXPECT(t.branchSupport(h["a"]) == 0); - BEAST_EXPECT(t.branchSupport(h["axy"]) == 0); - - t.insert(h["abc"]); - BEAST_EXPECT(t.tipSupport(h["a"]) == 0); - BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abcd"]) == 0); - - BEAST_EXPECT(t.branchSupport(h["a"]) == 1); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abcd"]) == 0); - - t.insert(h["abe"]); - BEAST_EXPECT(t.tipSupport(h["a"]) == 0); - BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 1); - BEAST_EXPECT(t.tipSupport(h["abe"]) == 1); - - BEAST_EXPECT(t.branchSupport(h["a"]) == 2); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 2); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abe"]) == 1); - - t.remove(h["abc"]); - BEAST_EXPECT(t.tipSupport(h["a"]) == 0); - BEAST_EXPECT(t.tipSupport(h["ab"]) == 0); - BEAST_EXPECT(t.tipSupport(h["abc"]) == 0); - BEAST_EXPECT(t.tipSupport(h["abe"]) == 1); - - BEAST_EXPECT(t.branchSupport(h["a"]) == 1); - BEAST_EXPECT(t.branchSupport(h["ab"]) == 1); - BEAST_EXPECT(t.branchSupport(h["abc"]) == 0); - BEAST_EXPECT(t.branchSupport(h["abe"]) == 1); - } - - void - testGetPreferred() - { - using namespace csf; - using Seq = Ledger::Seq; - // Empty - { - LedgerTrie const t; - BEAST_EXPECT(t.getPreferred(Seq{0}) == std::nullopt); - BEAST_EXPECT(t.getPreferred(Seq{2}) == std::nullopt); - } - // Genesis support is NOT empty - { - LedgerTrie t; - LedgerHistoryHelper h; - Ledger const genesis = h[""]; - t.insert(genesis); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{0})->id == genesis.id()); - BEAST_EXPECT(t.remove(genesis)); - BEAST_EXPECT(t.getPreferred(Seq{0}) == std::nullopt); - BEAST_EXPECT(!t.remove(genesis)); - } - // Single node no children - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); - } - // Single node smaller child support - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - t.insert(h["abcd"]); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); - } - // Single node larger child - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - t.insert(h["abcd"], 2); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcd"].id()); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcd"].id()); - } - // Single node smaller children support - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - t.insert(h["abcd"]); - t.insert(h["abce"]); - - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); - - t.insert(h["abc"]); - - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - } - // Single node larger children - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - t.insert(h["abcd"], 2); - t.insert(h["abce"]); - - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); - - t.insert(h["abcd"]); - - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcd"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcd"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - } - // Tie-breaker by id - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abcd"], 2); - t.insert(h["abce"], 2); - - BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abce"].id()); - - t.insert(h["abcd"]); - BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcd"].id()); - } - - // Tie-breaker not needed - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - t.insert(h["abcd"]); - t.insert(h["abce"], 2); - // abce only has a margin of 1, but it owns the tie-breaker - BEAST_EXPECT(h["abce"].id() > h["abcd"].id()); - - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abce"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abce"].id()); - - // Switch support from abce to abcd, tie-breaker now needed - t.remove(h["abce"]); - t.insert(h["abcd"]); - - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - } - - // Single node larger grand child - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - t.insert(h["abcd"], 2); - t.insert(h["abcde"], 4); - - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["abcde"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - } - - // Too much uncommitted support from competing branches - { - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["abc"]); - t.insert(h["abcde"], 2); - t.insert(h["abcfg"], 2); - // 'de' and 'fg' are tied without 'abc' vote - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abc"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["abc"].id()); - - t.remove(h["abc"]); - t.insert(h["abcd"]); - - // 'de' branch has 3 votes to 2, so earlier sequences see it as preferred - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abcde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["abcde"].id()); - - // However, if you validated a ledger with Seq 5, potentially on - // a different branch, you do not yet know if they chose abcd - // or abcf because of you, so abc remains preferred - BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["abc"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - } - - // Changing largestSeq perspective changes preferred branch - { - /** - * Build the tree below with initial tip support annotated - * A - * / \ - * B(1) C(1) - * / | | - * H D F(1) - * | - * E(2) - * | - * G - */ - LedgerTrie t; - LedgerHistoryHelper h; - t.insert(h["ab"]); - t.insert(h["ac"]); - t.insert(h["acf"]); - t.insert(h["abde"], 2); - - // B has more branch support - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["ab"].id()); - - // But if you last validated D,F or E, you do not yet know - // if someone used that validation to commit to B or C - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - - /** - * One of E advancing to G doesn't change anything - * A - * / \ - * B(1) C(1) - * / | | - * H D F(1) - * | - * E(1) - * | - * G(1) - */ - t.remove(h["abde"]); - t.insert(h["abdeg"]); - - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - - /** - * C advancing to H does advance the seq 3 preferred ledger - * A - * / \ - * B(1) C - * / | | - * H(1)D F(1) - * | - * E(1) - * | - * G(1) - */ - t.remove(h["ac"]); - t.insert(h["abh"]); - - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - - /** - * F advancing to E also moves the preferred ledger forward - * A - * / \ - * B(1) C - * / | | - * H(1)D F - * | - * E(2) - * | - * G(1) - */ - t.remove(h["acf"]); - t.insert(h["abde"]); - - // NOLINTBEGIN(bugprone-unchecked-optional-access) - BEAST_EXPECT(t.getPreferred(Seq{1})->id == h["abde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{2})->id == h["abde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{3})->id == h["abde"].id()); - BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["ab"].id()); - BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["ab"].id()); - // NOLINTEND(bugprone-unchecked-optional-access) - } - } - - void - testRootRelated() - { - using namespace csf; - // Since the root is a special node that breaks the no-single child - // invariant, do some tests that exercise it. - - LedgerTrie t; - LedgerHistoryHelper h; - BEAST_EXPECT(!t.remove(h[""])); - BEAST_EXPECT(t.branchSupport(h[""]) == 0); - BEAST_EXPECT(t.tipSupport(h[""]) == 0); - - t.insert(h["a"]); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.branchSupport(h[""]) == 1); - BEAST_EXPECT(t.tipSupport(h[""]) == 0); - - t.insert(h["e"]); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.branchSupport(h[""]) == 2); - BEAST_EXPECT(t.tipSupport(h[""]) == 0); - - BEAST_EXPECT(t.remove(h["e"])); - BEAST_EXPECT(t.checkInvariants()); - BEAST_EXPECT(t.branchSupport(h[""]) == 1); - BEAST_EXPECT(t.tipSupport(h[""]) == 0); - } - - void - testStress() - { - using namespace csf; - LedgerTrie t; - LedgerHistoryHelper h; - - // Test quasi-randomly add/remove supporting for different ledgers - // from a branching history. - - // Ledgers have sequence 1,2,3,4 - std::uint32_t const depthConst = 4; - // Each ledger has 4 possible children - std::uint32_t const width = 4; - - std::uint32_t const iterations = 10000; - - // Use explicit seed to have same results for CI - // NOLINTNEXTLINE(bugprone-random-generator-seed): fixed seed for reproducible test - std::mt19937 gen{42}; - std::uniform_int_distribution<> depthDist(0, depthConst - 1); - std::uniform_int_distribution<> widthDist(0, width - 1); - std::uniform_int_distribution<> flip(0, 1); - for (std::uint32_t i = 0; i < iterations; ++i) - { - // pick a random ledger history - std::string curr; - char const depth = depthDist(gen); - char offset = 0; - for (char d = 0; d < depth; ++d) - { - char const a = offset + widthDist(gen); - curr += a; - offset = (a + 1) * width; - } - - // 50-50 to add remove - if (flip(gen) == 0) - { - t.insert(h[curr]); - } - else - { - t.remove(h[curr]); - } - if (!BEAST_EXPECT(t.checkInvariants())) - return; - } - } - - void - run() override - { - testInsert(); - testRemove(); - testEmpty(); - testSupport(); - testGetPreferred(); - testRootRelated(); - testStress(); - } -}; - -BEAST_DEFINE_TESTSUITE(LedgerTrie, consensus, xrpl); -} // namespace xrpl::test diff --git a/src/test/consensus/RCLCensorshipDetector_test.cpp b/src/test/consensus/RCLCensorshipDetector_test.cpp deleted file mode 100644 index 722a34f937..0000000000 --- a/src/test/consensus/RCLCensorshipDetector_test.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include - -#include - -#include -#include -#include - -namespace xrpl::test { - -class RCLCensorshipDetector_test : public beast::unit_test::Suite -{ - void - test( - RCLCensorshipDetector& cdet, - int round, - std::vector proposed, - std::vector accepted, - std::vector remain, - std::vector remove) - { - // Begin tracking what we're proposing this round - RCLCensorshipDetector::TxIDSeqVec proposal; - for (auto const& i : proposed) - proposal.emplace_back(i, round); - cdet.propose(std::move(proposal)); - - // Finalize the round, by processing what we accepted; then - // remove anything that needs to be removed and ensure that - // what remains is correct. - cdet.check(std::move(accepted), [&remove, &remain](auto id, auto seq) { - // If the item is supposed to be removed from the censorship - // detector internal tracker manually, do it now: - if (std::ranges::find(remove, id) != remove.end()) - return true; - - // If the item is supposed to still remain in the censorship - // detector internal tracker; remove it from the vector. - auto it = std::ranges::find(remain, id); - if (it != remain.end()) - remain.erase(it); - return false; - }); - - // On entry, this set contained all the elements that should be tracked - // by the detector after we process this round. We removed all the items - // that actually were in the tracker, so this should now be empty: - BEAST_EXPECT(remain.empty()); - } - -public: - void - run() override - { - testcase("Censorship Detector"); - - RCLCensorshipDetector cdet; - int round = 0; - // proposed accepted remain remove - test(cdet, ++round, {}, {}, {}, {}); - test(cdet, ++round, {10, 11, 12, 13}, {11, 2}, {10, 13}, {}); - test(cdet, ++round, {10, 13, 14, 15}, {14}, {10, 13, 15}, {}); - test(cdet, ++round, {10, 13, 15, 16}, {15, 16}, {10, 13}, {}); - test(cdet, ++round, {10, 13}, {17, 18}, {10, 13}, {}); - test(cdet, ++round, {10, 19}, {}, {10, 19}, {}); - test(cdet, ++round, {10, 19, 20}, {20}, {10}, {19}); - test(cdet, ++round, {21}, {21}, {}, {}); - test(cdet, ++round, {}, {22}, {}, {}); - test(cdet, ++round, {23, 24, 25, 26}, {25, 27}, {23, 26}, {24}); - test(cdet, ++round, {23, 26, 28}, {26, 28}, {23}, {}); - - for (int i = 0; i != 10; ++i) - test(cdet, ++round, {23}, {}, {23}, {}); - - test(cdet, ++round, {23, 29}, {29}, {23}, {}); - test(cdet, ++round, {30, 31}, {31}, {30}, {}); - test(cdet, ++round, {30}, {30}, {}, {}); - test(cdet, ++round, {}, {}, {}, {}); - } -}; - -BEAST_DEFINE_TESTSUITE(RCLCensorshipDetector, consensus, xrpl); -} // namespace xrpl::test diff --git a/src/test/consensus/ScaleFreeSim_test.cpp b/src/test/consensus/ScaleFreeSim_test.cpp deleted file mode 100644 index e533e09eb0..0000000000 --- a/src/test/consensus/ScaleFreeSim_test.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - -namespace xrpl::test { - -class ScaleFreeSim_test : public beast::unit_test::Suite -{ - void - run() override - { - using namespace std::chrono; - using namespace csf; - - // Generate a quasi-random scale free network and simulate consensus - // as we vary transaction submission rates - - int const n = 100; // Peers - - int const numUNLs = 15; // UNL lists - int const minUNLSize = n / 4, maxUNLSize = n / 2; - - ConsensusParms const parms{}; - Sim sim; - PeerGroup network = sim.createGroup(n); - - // generate trust ranks - std::vector const ranks = - sample(network.size(), PowerLawDistribution{1, 3}, sim.rng); - - // generate scale-free trust graph - randomRankedTrust( - network, - ranks, - numUNLs, - std::uniform_int_distribution<>{minUNLSize, maxUNLSize}, - sim.rng); - - // nodes with a trust line in either direction are network-connected - network.connectFromTrust(round(0.2 * parms.ledgerGRANULARITY)); - - // Initialize collectors to track statistics to report - TxCollector txCollector; - LedgerCollector ledgerCollector; - auto colls = makeCollectors(txCollector, ledgerCollector); - sim.collectors.add(colls); - - // Initial round to set prior state - sim.run(1); - - // Initialize timers - HeartbeatTimer heart(sim.scheduler, seconds(10s)); - - // Run for 10 minutes, submitting 100 tx/second - std::chrono::nanoseconds const simDuration = 10min; - std::chrono::nanoseconds const quiet = 10s; - Rate const rate{.count = 100, .duration = 1000ms}; - - // txs, start/stop/step, target - auto peerSelector = makeSelector(network.begin(), network.end(), ranks, sim.rng); - auto txSubmitter = makeSubmitter( - ConstantDistribution{rate.inv()}, - sim.scheduler.now() + quiet, - sim.scheduler.now() + (simDuration - quiet), - peerSelector, - sim.scheduler, - sim.rng); - - // run simulation for given duration - heart.start(); - sim.run(simDuration); - - BEAST_EXPECT(sim.branches() == 1); - BEAST_EXPECT(sim.synchronized()); - - // TODO: Clean up this formatting mess!! - - log << "Peers: " << network.size() << std::endl; - log << "Simulated Duration: " << duration_cast(simDuration).count() << " ms" - << std::endl; - log << "Branches: " << sim.branches() << std::endl; - log << "Synchronized: " << (sim.synchronized() ? "Y" : "N") << std::endl; - log << std::endl; - - txCollector.report(simDuration, log); - ledgerCollector.report(simDuration, log); - // Print summary? - // # forks? # of LCLs? - // # peers - // # tx submitted - // # ledgers/sec etc.? - } -}; - -BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(ScaleFreeSim, consensus, xrpl, 80); - -} // namespace xrpl::test diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp deleted file mode 100644 index 606c6f2824..0000000000 --- a/src/test/consensus/Validations_test.cpp +++ /dev/null @@ -1,1059 +0,0 @@ -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl::test::csf { -class Validations_test : public beast::unit_test::Suite -{ - using clock_type = beast::AbstractClock const; - - // Helper to convert steady_clock to a reasonable NetClock - // This allows a single manual clock in the unit tests - static NetClock::time_point - toNetClock(clock_type const& c) - { - // We don't care about the actual epochs, but do want the - // generated NetClock time to be well past its epoch to ensure - // any subtractions are positive - using namespace std::chrono; - return NetClock::time_point( - duration_cast(c.now().time_since_epoch() + 86400s)); - } - - // Represents a node that can issue validations - class Node - { - clock_type const& c_; - PeerID nodeID_; - bool trusted_ = true; - std::size_t signIdx_{1}; - std::optional loadFee_; - - public: - Node(PeerID nodeID, clock_type const& c) : c_(c), nodeID_(nodeID) - { - } - - void - untrust() - { - trusted_ = false; - } - - void - trust() - { - trusted_ = true; - } - - void - setLoadFee(std::uint32_t fee) - { - loadFee_ = fee; - } - - [[nodiscard]] PeerID - nodeID() const - { - return nodeID_; - } - - void - advanceKey() - { - signIdx_++; - } - - [[nodiscard]] PeerKey - currKey() const - { - return std::make_pair(nodeID_, signIdx_); - } - - [[nodiscard]] PeerKey - masterKey() const - { - return std::make_pair(nodeID_, 0); - } - [[nodiscard]] NetClock::time_point - now() const - { - return toNetClock(c_); - } - - // Issue a new validation with given sequence number and id and - // with signing and seen times offset from the common clock - [[nodiscard]] Validation - validate( - Ledger::ID id, - Ledger::Seq seq, - NetClock::duration signOffset, - NetClock::duration seenOffset, - bool full) const - { - Validation v{ - id, - seq, - now() + signOffset, - now() + seenOffset, - currKey(), - nodeID_, - full, - loadFee_}; - if (trusted_) - v.setTrusted(); - return v; - } - - [[nodiscard]] Validation - validate(Ledger ledger, NetClock::duration signOffset, NetClock::duration seenOffset) const - { - return validate(ledger.id(), ledger.seq(), signOffset, seenOffset, true); - } - - [[nodiscard]] Validation - validate(Ledger ledger) const - { - return validate( - ledger.id(), ledger.seq(), NetClock::duration{0}, NetClock::duration{0}, true); - } - - [[nodiscard]] Validation - partial(Ledger ledger) const - { - return validate( - ledger.id(), ledger.seq(), NetClock::duration{0}, NetClock::duration{0}, false); - } - }; - - // Generic Validations adaptor - class Adaptor - { - clock_type& c_; - LedgerOracle& oracle_; - - public: - // Non-locking mutex to avoid locks in generic Validations - struct Mutex - { - void - lock() - { - } - - void - unlock() - { - } - }; - - using Validation = csf::Validation; - using Ledger = csf::Ledger; - - Adaptor(clock_type& c, LedgerOracle& o) : c_{c}, oracle_{o} - { - } - - [[nodiscard]] NetClock::time_point - now() const - { - return toNetClock(c_); - } - - std::optional - acquire(Ledger::ID const& id) - { - return oracle_.lookup(id); - } - }; - - // Specialize generic Validations using the above types - using TestValidations = Validations; - - // Gather the dependencies of TestValidations in a single class and provide - // accessors for simplifying test logic - class TestHarness - { - ValidationParms p_; - beast::ManualClock clock_; - TestValidations tv_; - PeerID nextNodeId_{0}; - - public: - explicit TestHarness(LedgerOracle& o) : tv_(p_, clock_, clock_, o) - { - } - - ValStatus - add(Validation const& v) - { - return tv_.add(v.nodeID(), v); - } - - TestValidations& - vals() - { - return tv_; - } - - Node - makeNode() - { - return Node(nextNodeId_++, clock_); - } - - ValidationParms - parms() const - { - return p_; - } - - auto& - clock() - { - return clock_; - } - }; - - Ledger const genesisLedger_{Ledger::MakeGenesis{}}; - - void - testAddValidation() - { - using namespace std::chrono_literals; - - testcase("Add validation"); - LedgerHistoryHelper h; - Ledger const ledgerA = h["a"]; - Ledger ledgerAB = h["ab"]; - Ledger ledgerAZ = h["az"]; - Ledger ledgerABC = h["abc"]; - Ledger const ledgerABCD = h["abcd"]; - Ledger const ledgerABCDE = h["abcde"]; - - { - TestHarness harness(h.oracle); - Node n = harness.makeNode(); - - auto const v = n.validate(ledgerA); - - // Add a current validation - BEAST_EXPECT(ValStatus::Current == harness.add(v)); - - // Re-adding violates the increasing seq requirement for full - // validations - BEAST_EXPECT(ValStatus::BadSeq == harness.add(v)); - - harness.clock().advance(1s); - - BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerAB))); - - // Test the node changing signing key - - // Confirm old ledger on hand, but not new ledger - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerAB.id()) == 1); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerABC.id()) == 0); - - // Rotate signing keys - n.advanceKey(); - - harness.clock().advance(1s); - - // Cannot re-do the same full validation sequence - BEAST_EXPECT(ValStatus::Conflicting == harness.add(n.validate(ledgerAB))); - // Cannot send the same partial validation sequence - BEAST_EXPECT(ValStatus::Conflicting == harness.add(n.partial(ledgerAB))); - - // Now trusts the newest ledger too - harness.clock().advance(1s); - BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerABC))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerAB.id()) == 1); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerABC.id()) == 1); - - // Processing validations out of order should ignore the older - // validation - harness.clock().advance(2s); - auto const valABCDE = n.validate(ledgerABCDE); - - harness.clock().advance(4s); - auto const valABCD = n.validate(ledgerABCD); - - BEAST_EXPECT(ValStatus::Current == harness.add(valABCD)); - - BEAST_EXPECT(ValStatus::Stale == harness.add(valABCDE)); - } - - { - // Process validations out of order with shifted times - - TestHarness harness(h.oracle); - Node const n = harness.makeNode(); - - // Establish a new current validation - BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerA))); - - // Process a validation that has "later" seq but early sign time - BEAST_EXPECT(ValStatus::Stale == harness.add(n.validate(ledgerAB, -1s, -1s))); - - // Process a validation that has a later seq and later sign - // time - BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerABC, 1s, 1s))); - } - - { - // Test stale on arrival validations - TestHarness harness(h.oracle); - Node const n = harness.makeNode(); - - BEAST_EXPECT( - ValStatus::Stale == - harness.add(n.validate(ledgerA, -harness.parms().validationCurrentEarly, 0s))); - - BEAST_EXPECT( - ValStatus::Stale == - harness.add(n.validate(ledgerA, harness.parms().validationCurrentWall, 0s))); - - BEAST_EXPECT( - ValStatus::Stale == - harness.add(n.validate(ledgerA, 0s, harness.parms().validationCurrentLocal))); - } - - { - // Test that full or partials cannot be sent for older sequence - // numbers, unless time-out has happened - for (bool doFull : {true, false}) - { - TestHarness harness(h.oracle); - Node n = harness.makeNode(); - - auto process = [&](Ledger& lgr) { - if (doFull) - return harness.add(n.validate(lgr)); - return harness.add(n.partial(lgr)); - }; - - BEAST_EXPECT(ValStatus::Current == process(ledgerABC)); - harness.clock().advance(1s); - BEAST_EXPECT(ledgerAB.seq() < ledgerABC.seq()); - BEAST_EXPECT(ValStatus::BadSeq == process(ledgerAB)); - - // If we advance far enough for AB to expire, we can fully - // validate or partially validate that sequence number again - BEAST_EXPECT(ValStatus::Conflicting == process(ledgerAZ)); - harness.clock().advance(harness.parms().validationSetExpires + 1ms); - BEAST_EXPECT(ValStatus::Current == process(ledgerAZ)); - } - } - } - - void - testOnStale() - { - testcase("Stale validation"); - // Verify validation becomes stale based solely on time passing, but - // use different functions to trigger the check for staleness - - LedgerHistoryHelper h; - Ledger ledgerA = h["a"]; - Ledger const ledgerAB = h["ab"]; - - using Trigger = std::function; - - std::vector const triggers = { - [&](TestValidations& vals) { vals.currentTrusted(); }, - [&](TestValidations& vals) { vals.getCurrentNodeIDs(); }, - [&](TestValidations& vals) { vals.getPreferred(genesisLedger_); }, - [&](TestValidations& vals) { vals.getNodesAfter(ledgerA, ledgerA.id()); }}; - for (Trigger const& trigger : triggers) - { - TestHarness harness(h.oracle); - Node const n = harness.makeNode(); - - BEAST_EXPECT(ValStatus::Current == harness.add(n.validate(ledgerAB))); - trigger(harness.vals()); - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1); - BEAST_EXPECT( - harness.vals().getPreferred(genesisLedger_) == - std::make_pair(ledgerAB.seq(), ledgerAB.id())); - harness.clock().advance(harness.parms().validationCurrentLocal); - - // trigger check for stale - trigger(harness.vals()); - - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 0); - BEAST_EXPECT(harness.vals().getPreferred(genesisLedger_) == std::nullopt); - } - } - - void - testGetNodesAfter() - { - // Test getting number of nodes working on a validation descending - // a prescribed one. This count should only be for trusted nodes, but - // includes partial and full validations - - using namespace std::chrono_literals; - testcase("Get nodes after"); - - LedgerHistoryHelper h; - Ledger const ledgerA = h["a"]; - Ledger const ledgerAB = h["ab"]; - Ledger const ledgerABC = h["abc"]; - Ledger const ledgerAD = h["ad"]; - - TestHarness harness(h.oracle); - Node const trustedNode1 = harness.makeNode(); - Node const trustedNode2 = harness.makeNode(); - Node const trustedNode3 = harness.makeNode(); - - Node notTrustedNode = harness.makeNode(); - notTrustedNode.untrust(); - - // first round a,b,c agree, d has is partial - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode1.validate(ledgerA))); - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode2.validate(ledgerA))); - BEAST_EXPECT(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerA))); - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode3.partial(ledgerA))); - - for (Ledger const& ledger : {ledgerA, ledgerAB, ledgerABC, ledgerAD}) - BEAST_EXPECT(harness.vals().getNodesAfter(ledger, ledger.id()) == 0); - - harness.clock().advance(5s); - - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode1.validate(ledgerAB))); - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode2.validate(ledgerABC))); - BEAST_EXPECT(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerAB))); - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode3.partial(ledgerABC))); - - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 3); - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAB, ledgerAB.id()) == 2); - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerABC, ledgerABC.id()) == 0); - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerAD.id()) == 0); - - // If given a ledger inconsistent with the id, is still able to check using slower method - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerA.id()) == 1); - BEAST_EXPECT(harness.vals().getNodesAfter(ledgerAD, ledgerAB.id()) == 2); - } - - void - testCurrentTrusted() - { - using namespace std::chrono_literals; - testcase("Current trusted validations"); - - LedgerHistoryHelper h; - Ledger const ledgerA = h["a"]; - Ledger const ledgerB = h["b"]; - Ledger const ledgerAC = h["ac"]; - - TestHarness harness(h.oracle); - Node const a = harness.makeNode(); - Node b = harness.makeNode(); - b.untrust(); - - BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerA))); - BEAST_EXPECT(ValStatus::Current == harness.add(b.validate(ledgerB))); - - // Only a is trusted - BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); - BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ledgerA.id()); - BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == ledgerA.seq()); - - harness.clock().advance(3s); - - for (auto const& node : {a, b}) - BEAST_EXPECT(ValStatus::Current == harness.add(node.validate(ledgerAC))); - - // New validation for a - BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); - BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ledgerAC.id()); - BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == ledgerAC.seq()); - - // Pass enough time for it to go stale - harness.clock().advance(harness.parms().validationCurrentLocal); - BEAST_EXPECT(harness.vals().currentTrusted().empty()); - } - - void - testGetCurrentPublicKeys() - { - using namespace std::chrono_literals; - testcase("Current public keys"); - - LedgerHistoryHelper h; - Ledger const ledgerA = h["a"]; - Ledger const ledgerAC = h["ac"]; - - TestHarness harness(h.oracle); - Node a = harness.makeNode(), b = harness.makeNode(); - b.untrust(); - - for (auto const& node : {a, b}) - BEAST_EXPECT(ValStatus::Current == harness.add(node.validate(ledgerA))); - - { - hash_set const expectedKeys = {a.nodeID(), b.nodeID()}; - BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys); - } - - harness.clock().advance(3s); - - // Change keys and issue partials - a.advanceKey(); - b.advanceKey(); - - for (auto const& node : {a, b}) - BEAST_EXPECT(ValStatus::Current == harness.add(node.partial(ledgerAC))); - - { - hash_set const expectedKeys = {a.nodeID(), b.nodeID()}; - BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys); - } - - // Pass enough time for them to go stale - harness.clock().advance(harness.parms().validationCurrentLocal); - BEAST_EXPECT(harness.vals().getCurrentNodeIDs().empty()); - } - - void - testTrustedByLedgerFunctions() - { - // Test the Validations functions that calculate a value by ledger ID - using namespace std::chrono_literals; - testcase("By ledger functions"); - - // Several Validations functions return a set of values associated - // with trusted ledgers sharing the same ledger ID. The tests below - // exercise this logic by saving the set of trusted Validations, and - // verifying that the Validations member functions all calculate the - // proper transformation of the available ledgers. - - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - - Node a = harness.makeNode(), b = harness.makeNode(), c = harness.makeNode(), - d = harness.makeNode(), e = harness.makeNode(); - - c.untrust(); - // Mix of load fees - a.setLoadFee(12); - b.setLoadFee(1); - c.setLoadFee(12); - e.setLoadFee(12); - - hash_map, std::vector> trustedValidations; - - //---------------------------------------------------------------------- - // checkers - auto sorted = [](auto vec) { - std::sort(vec.begin(), vec.end()); - return vec; - }; - auto compare = [&]() { - for (auto& it : trustedValidations) - { - auto const& id = it.first.first; - auto const& seq = it.first.second; - auto const& expectedValidations = it.second; - - BEAST_EXPECT(harness.vals().numTrustedForLedger(id) == expectedValidations.size()); - BEAST_EXPECT( - sorted(harness.vals().getTrustedForLedger(id, seq)) == - sorted(expectedValidations)); - - std::uint32_t const baseFee = 0; - std::vector expectedFees; - expectedFees.reserve(expectedValidations.size()); - for (auto const& val : expectedValidations) - { - expectedFees.push_back(val.loadFee().value_or(baseFee)); - } - - BEAST_EXPECT(sorted(harness.vals().fees(id, baseFee)) == sorted(expectedFees)); - } - }; - - //---------------------------------------------------------------------- - Ledger const ledgerA = h["a"]; - Ledger const ledgerB = h["b"]; - Ledger const ledgerAC = h["ac"]; - - // Add a dummy ID to cover unknown ledger identifiers - trustedValidations[{Ledger::ID{100}, Ledger::Seq{100}}] = {}; - - // first round a,b,c agree - for (auto const& node : {a, b, c}) - { - auto const val = node.validate(ledgerA); - BEAST_EXPECT(ValStatus::Current == harness.add(val)); - if (val.trusted()) - trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); - } - // d disagrees - { - auto const val = d.validate(ledgerB); - BEAST_EXPECT(ValStatus::Current == harness.add(val)); - trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); - } - // e only issues partials - { - BEAST_EXPECT(ValStatus::Current == harness.add(e.partial(ledgerA))); - } - - harness.clock().advance(5s); - // second round, a,b,c move to ledger 2 - for (auto const& node : {a, b, c}) - { - auto const val = node.validate(ledgerAC); - BEAST_EXPECT(ValStatus::Current == harness.add(val)); - if (val.trusted()) - trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); - } - // d now thinks ledger 1, but cannot re-issue a previously used seq - // and attempting it should generate a conflict. - { - BEAST_EXPECT(ValStatus::Conflicting == harness.add(d.partial(ledgerA))); - } - // e only issues partials - { - BEAST_EXPECT(ValStatus::Current == harness.add(e.partial(ledgerAC))); - } - - compare(); - } - - void - testExpire() - { - // Verify expiring clears out validations stored by ledger - testcase("Expire validations"); - SuiteJournal j("Validations_test", *this); - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const a = harness.makeNode(); - constexpr Ledger::Seq kOne(1); - constexpr Ledger::Seq kTwo(2); - - // simple cases - Ledger const ledgerA = h["a"]; - BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerA))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); - harness.vals().expire(j); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); - harness.clock().advance(harness.parms().validationSetExpires); - harness.vals().expire(j); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); - - // use setSeqToKeep to keep the validation from expire - Ledger const ledgerB = h["ab"]; - BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerB))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); - harness.vals().setSeqToKeep(ledgerB.seq(), ledgerB.seq() + kOne); - harness.clock().advance(harness.parms().validationSetExpires); - harness.vals().expire(j); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); - // change toKeep - harness.vals().setSeqToKeep(ledgerB.seq() + kOne, ledgerB.seq() + kTwo); - // advance clock slowly - int const loops = - harness.parms().validationSetExpires / harness.parms().validationFRESHNESS + 1; - for (int i = 0; i < loops; ++i) - { - harness.clock().advance(harness.parms().validationFRESHNESS); - harness.vals().expire(j); - } - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 0); - - // Allow the validation with high seq to expire - Ledger const ledgerC = h["abc"]; - BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerC))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerC.id()) == 1); - harness.vals().setSeqToKeep(ledgerC.seq() - kOne, ledgerC.seq()); - harness.clock().advance(harness.parms().validationSetExpires); - harness.vals().expire(j); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerC.id()) == 0); - } - - void - testFlush() - { - // Test final flush of validations - using namespace std::chrono_literals; - testcase("Flush validations"); - - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const trustedNode1 = harness.makeNode(); - Node const trustedNode2 = harness.makeNode(); - Node notTrustedNode = harness.makeNode(); - notTrustedNode.untrust(); - - Ledger const ledgerA = h["a"]; - Ledger const ledgerAB = h["ab"]; - - hash_map expected; - for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode}) - { - auto const val = node.validate(ledgerA); - BEAST_EXPECT(ValStatus::Current == harness.add(val)); - expected.emplace(node.nodeID(), val); - } - - // Send in a new validation for a, saving the new one into the expected - // map after setting the proper prior ledger ID it replaced - harness.clock().advance(1s); - auto newVal = trustedNode1.validate(ledgerAB); - BEAST_EXPECT(ValStatus::Current == harness.add(newVal)); - expected.find(trustedNode1.nodeID())->second = newVal; - } - - void - testGetPreferredLedger() - { - using namespace std::chrono_literals; - testcase("Preferred Ledger"); - - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const trustedNode1 = harness.makeNode(); - Node const trustedNode2 = harness.makeNode(); - Node const trustedNode3 = harness.makeNode(); - - Node notTrustedNode = harness.makeNode(); - notTrustedNode.untrust(); - - Ledger const ledgerA = h["a"]; - Ledger const ledgerB = h["b"]; - Ledger const ledgerAC = h["ac"]; - Ledger const ledgerACD = h["acd"]; - - using Seq = Ledger::Seq; - - auto pref = [](Ledger ledger) { return std::make_pair(ledger.seq(), ledger.id()); }; - - // Empty (no ledgers) - BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == std::nullopt); - - // Single ledger - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode1.validate(ledgerB))); - BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB)); - BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB)); - - // Minimum valid sequence - BEAST_EXPECT(harness.vals().getPreferred(ledgerA, Seq{10}) == ledgerA.id()); - - // Untrusted doesn't impact preferred ledger - // (ledgerB has tie-break over ledgerA) - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode2.validate(ledgerA))); - BEAST_EXPECT(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerA))); - BEAST_EXPECT(ledgerB.id() > ledgerA.id()); - BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerB)); - BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerB)); - - // Partial does break ties - BEAST_EXPECT(ValStatus::Current == harness.add(trustedNode3.partial(ledgerA))); - BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); - BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerA)); - - harness.clock().advance(5s); - - // Parent of preferred-> stick with ledger - for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode, trustedNode3}) - BEAST_EXPECT(ValStatus::Current == harness.add(node.validate(ledgerAC))); - // Parent of preferred stays put - BEAST_EXPECT(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); - // Earlier different chain, switch - BEAST_EXPECT(harness.vals().getPreferred(ledgerB) == pref(ledgerAC)); - // Later on chain, stays where it is - BEAST_EXPECT(harness.vals().getPreferred(ledgerACD) == pref(ledgerACD)); - - // Any later grandchild or different chain is preferred - harness.clock().advance(5s); - for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode, trustedNode3}) - BEAST_EXPECT(ValStatus::Current == harness.add(node.validate(ledgerACD))); - for (auto const& ledger : {ledgerA, ledgerB, ledgerACD}) - BEAST_EXPECT(harness.vals().getPreferred(ledger) == pref(ledgerACD)); - } - - void - testGetPreferredLCL() - { - using namespace std::chrono_literals; - testcase("Get preferred LCL"); - - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const a = harness.makeNode(); - - Ledger const ledgerA = h["a"]; - Ledger const ledgerB = h["b"]; - Ledger const ledgerC = h["c"]; - - using ID = Ledger::ID; - using Seq = Ledger::Seq; - - hash_map peerCounts; - - // No trusted validations or counts sticks with current ledger - BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerA.id()); - - ++peerCounts[ledgerB.id()]; - - // No trusted validations, rely on peer counts - BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerB.id()); - - ++peerCounts[ledgerC.id()]; - // No trusted validations, tied peers goes with larger ID - BEAST_EXPECT(ledgerC.id() > ledgerB.id()); - - BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerC.id()); - - peerCounts[ledgerC.id()] += 1000; - - // Single trusted always wins over peer counts - BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerA))); - BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerA.id()); - BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerB, Seq{0}, peerCounts) == ledgerA.id()); - BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerC, Seq{0}, peerCounts) == ledgerA.id()); - - // Stick with current ledger if trusted validation ledger has too old - // of a sequence - BEAST_EXPECT(harness.vals().getPreferredLCL(ledgerB, Seq{2}, peerCounts) == ledgerB.id()); - } - - void - testAcquireValidatedLedger() - { - using namespace std::chrono_literals; - testcase("Acquire validated ledger"); - - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const a = harness.makeNode(); - Node const b = harness.makeNode(); - - using ID = Ledger::ID; - using Seq = Ledger::Seq; - - // Validate the ledger before it is actually available - Validation const val = a.validate(ID{2}, Seq{2}, 0s, 0s, true); - - BEAST_EXPECT(ValStatus::Current == harness.add(val)); - // Validation is available - BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1); - // but ledger based data is not - BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger_, ID{0}) == 0); - // Initial preferred branch falls back to the ledger we are trying to - // acquire - BEAST_EXPECT(harness.vals().getPreferred(genesisLedger_) == std::make_pair(Seq{2}, ID{2})); - - // After adding another unavailable validation, the preferred ledger - // breaks ties via higher ID - BEAST_EXPECT(ValStatus::Current == harness.add(b.validate(ID{3}, Seq{2}, 0s, 0s, true))); - BEAST_EXPECT(harness.vals().getPreferred(genesisLedger_) == std::make_pair(Seq{2}, ID{3})); - - // Create the ledger - Ledger const ledgerAB = h["ab"]; - // Now it should be available - BEAST_EXPECT(harness.vals().getNodesAfter(genesisLedger_, ID{0}) == 1); - - // Create a validation that is not available - harness.clock().advance(5s); - Validation const val2 = a.validate(ID{4}, Seq{4}, 0s, 0s, true); - BEAST_EXPECT(ValStatus::Current == harness.add(val2)); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 1); - BEAST_EXPECT( - harness.vals().getPreferred(genesisLedger_) == - std::make_pair(ledgerAB.seq(), ledgerAB.id())); - - // Another node requesting that ledger still doesn't change things - Validation const val3 = b.validate(ID{4}, Seq{4}, 0s, 0s, true); - BEAST_EXPECT(ValStatus::Current == harness.add(val3)); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{4}) == 2); - BEAST_EXPECT( - harness.vals().getPreferred(genesisLedger_) == - std::make_pair(ledgerAB.seq(), ledgerAB.id())); - - // Switch to validation that is available - harness.clock().advance(5s); - Ledger const ledgerABCDE = h["abcde"]; - BEAST_EXPECT(ValStatus::Current == harness.add(a.partial(ledgerABCDE))); - BEAST_EXPECT(ValStatus::Current == harness.add(b.partial(ledgerABCDE))); - BEAST_EXPECT( - harness.vals().getPreferred(genesisLedger_) == - std::make_pair(ledgerABCDE.seq(), ledgerABCDE.id())); - } - - void - testNumTrustedForLedger() - { - testcase("NumTrustedForLedger"); - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const a = harness.makeNode(); - Node const b = harness.makeNode(); - Ledger const ledgerA = h["a"]; - - BEAST_EXPECT(ValStatus::Current == harness.add(a.partial(ledgerA))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); - - BEAST_EXPECT(ValStatus::Current == harness.add(b.validate(ledgerA))); - BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); - } - - void - testSeqEnforcer() - { - testcase("SeqEnforcer"); - using Seq = Ledger::Seq; - using namespace std::chrono; - - beast::ManualClock clock; - SeqEnforcer enforcer; - - ValidationParms const p; - - BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); - BEAST_EXPECT(enforcer(clock.now(), Seq{10}, p)); - BEAST_EXPECT(!enforcer(clock.now(), Seq{5}, p)); - BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p)); - clock.advance(p.validationSetExpires - 1ms); - BEAST_EXPECT(!enforcer(clock.now(), Seq{1}, p)); - clock.advance(2ms); - BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); - } - - void - testTrustChanged() - { - testcase("TrustChanged"); - using namespace std::chrono; - - auto checker = [this]( - TestValidations& vals, - hash_set const& listed, - std::vector const& trustedVals) { - Ledger::ID const testID = - trustedVals.empty() ? this->genesisLedger_.id() : trustedVals[0].ledgerID(); - Ledger::Seq const testSeq = - trustedVals.empty() ? this->genesisLedger_.seq() : trustedVals[0].seq(); - BEAST_EXPECT(vals.currentTrusted() == trustedVals); - BEAST_EXPECT(vals.getCurrentNodeIDs() == listed); - BEAST_EXPECT( - vals.getNodesAfter(this->genesisLedger_, genesisLedger_.id()) == - trustedVals.size()); - if (trustedVals.empty()) - { - BEAST_EXPECT(vals.getPreferred(this->genesisLedger_) == std::nullopt); - } - else - { - BEAST_EXPECT(vals.getPreferred(this->genesisLedger_)->second == testID); - } - BEAST_EXPECT(vals.getTrustedForLedger(testID, testSeq) == trustedVals); - BEAST_EXPECT(vals.numTrustedForLedger(testID) == trustedVals.size()); - }; - - { - // Trusted to untrusted - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const a = harness.makeNode(); - Ledger const ledgerAB = h["ab"]; - Validation const v = a.validate(ledgerAB); - BEAST_EXPECT(ValStatus::Current == harness.add(v)); - - hash_set const listed({a.nodeID()}); - std::vector trustedVals({v}); - checker(harness.vals(), listed, trustedVals); - - trustedVals.clear(); - harness.vals().trustChanged({}, {a.nodeID()}); - checker(harness.vals(), listed, trustedVals); - } - - { - // Untrusted to trusted - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node a = harness.makeNode(); - a.untrust(); - Ledger const ledgerAB = h["ab"]; - Validation const v = a.validate(ledgerAB); - BEAST_EXPECT(ValStatus::Current == harness.add(v)); - - hash_set const listed({a.nodeID()}); - std::vector trustedVals; - checker(harness.vals(), listed, trustedVals); - - trustedVals.push_back(v); - harness.vals().trustChanged({a.nodeID()}, {}); - checker(harness.vals(), listed, trustedVals); - } - - { - // Trusted but not acquired -> untrusted - LedgerHistoryHelper h; - TestHarness harness(h.oracle); - Node const a = harness.makeNode(); - Validation const v = a.validate(Ledger::ID{2}, Ledger::Seq{2}, 0s, 0s, true); - BEAST_EXPECT(ValStatus::Current == harness.add(v)); - - hash_set const listed({a.nodeID()}); - std::vector trustedVals({v}); - auto& vals = harness.vals(); - BEAST_EXPECT(vals.currentTrusted() == trustedVals); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECT(vals.getPreferred(genesisLedger_)->second == v.ledgerID()); - BEAST_EXPECT(vals.getNodesAfter(genesisLedger_, genesisLedger_.id()) == 0); - - trustedVals.clear(); - harness.vals().trustChanged({}, {a.nodeID()}); - // make acquiring ledger available - h["ab"]; - BEAST_EXPECT(vals.currentTrusted() == trustedVals); - BEAST_EXPECT(vals.getPreferred(genesisLedger_) == std::nullopt); - BEAST_EXPECT(vals.getNodesAfter(genesisLedger_, genesisLedger_.id()) == 0); - } - } - - void - run() override - { - testAddValidation(); - testOnStale(); - testGetNodesAfter(); - testCurrentTrusted(); - testGetCurrentPublicKeys(); - testTrustedByLedgerFunctions(); - testExpire(); - testFlush(); - testGetPreferredLedger(); - testGetPreferredLCL(); - testAcquireValidatedLedger(); - testNumTrustedForLedger(); - testSeqEnforcer(); - testTrustChanged(); - } -}; - -BEAST_DEFINE_TESTSUITE(Validations, consensus, xrpl); -} // namespace xrpl::test::csf diff --git a/src/test/csf/BasicNetwork_test.cpp b/src/test/csf/BasicNetwork_test.cpp deleted file mode 100644 index 22f52a1b63..0000000000 --- a/src/test/csf/BasicNetwork_test.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include -#include - -#include - -#include -#include - -namespace xrpl::test { - -class BasicNetwork_test : public beast::unit_test::Suite -{ -public: - struct Peer - { - int id; - std::set set; - - Peer(Peer const&) = default; - Peer(Peer&&) = default; - - explicit Peer(int id) : id(id) - { - } - - template - void - start(csf::Scheduler& scheduler, Net& net) - { - using namespace std::chrono_literals; - auto t = scheduler.in(1s, [&] { set.insert(0); }); - if (id == 0) - { - for (auto const link : net.links(this)) - { - net.send( - this, link.target, [&, to = link.target] { to->receive(net, this, 1); }); - } - } - else - { - scheduler.cancel(t); - } - } - - template - void - receive(Net& net, Peer* from, int m) - { - set.insert(m); - ++m; - if (m < 5) - { - for (auto const link : net.links(this)) - { - net.send(this, link.target, [&, mm = m, to = link.target] { - to->receive(net, this, mm); - }); - } - } - } - }; - - void - testNetwork() - { - using namespace std::chrono_literals; - std::vector pv; - pv.emplace_back(0); - pv.emplace_back(1); - pv.emplace_back(2); - csf::Scheduler scheduler; - csf::BasicNetwork net(scheduler); - BEAST_EXPECT(!net.connect(&pv[0], &pv[0])); - BEAST_EXPECT(net.connect(&pv[0], &pv[1], 1s)); - BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s)); - BEAST_EXPECT(!net.connect(&pv[0], &pv[1])); - for (auto& peer : pv) - peer.start(scheduler, net); - BEAST_EXPECT(scheduler.stepFor(0s)); - BEAST_EXPECT(scheduler.stepFor(1s)); - BEAST_EXPECT(scheduler.step()); - BEAST_EXPECT(!scheduler.step()); - BEAST_EXPECT(!scheduler.stepFor(1s)); - net.send(&pv[0], &pv[1], [] {}); - net.send(&pv[1], &pv[0], [] {}); - BEAST_EXPECT(net.disconnect(&pv[0], &pv[1])); - BEAST_EXPECT(!net.disconnect(&pv[0], &pv[1])); - for (;;) - { - auto const links = net.links(&pv[1]); - if (links.empty()) - break; - BEAST_EXPECT(net.disconnect(&pv[1], links[0].target)); - } - BEAST_EXPECT(pv[0].set == std::set({0, 2, 4})); - BEAST_EXPECT(pv[1].set == std::set({1, 3})); - BEAST_EXPECT(pv[2].set == std::set({2, 4})); - } - - void - testDisconnect() - { - using namespace std::chrono_literals; - csf::Scheduler scheduler; - csf::BasicNetwork net(scheduler); - BEAST_EXPECT(net.connect(0, 1, 1s)); - BEAST_EXPECT(net.connect(0, 2, 2s)); - - std::set delivered; - net.send(0, 1, [&]() { delivered.insert(1); }); - net.send(0, 2, [&]() { delivered.insert(2); }); - - scheduler.in(1000ms, [&]() { BEAST_EXPECT(net.disconnect(0, 2)); }); - scheduler.in(1100ms, [&]() { BEAST_EXPECT(net.connect(0, 2)); }); - - scheduler.step(); - - // only the first message is delivered because the disconnect at 1 s - // purges all pending messages from 0 to 2 - BEAST_EXPECT(delivered == std::set({1})); - } - - void - run() override - { - testNetwork(); - testDisconnect(); - } -}; - -BEAST_DEFINE_TESTSUITE(BasicNetwork, csf, xrpl); - -} // namespace xrpl::test diff --git a/src/test/csf/Digraph_test.cpp b/src/test/csf/Digraph_test.cpp deleted file mode 100644 index 40bfafde9c..0000000000 --- a/src/test/csf/Digraph_test.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include - -#include - -#include -#include -#include -#include - -namespace xrpl::test { - -class Digraph_test : public beast::unit_test::Suite -{ -public: - void - run() override - { - using namespace csf; - using Graph = Digraph; - Graph graph; - - BEAST_EXPECT(!graph.connected('a', 'b')); - BEAST_EXPECT(!graph.edge('a', 'b')); - BEAST_EXPECT(!graph.disconnect('a', 'b')); - - BEAST_EXPECT(graph.connect('a', 'b', "foobar")); - BEAST_EXPECT(graph.connected('a', 'b')); - BEAST_EXPECT( - *graph.edge('a', 'b') == "foobar"); // NOLINT(bugprone-unchecked-optional-access) - - BEAST_EXPECT(!graph.connect('a', 'b', "repeat")); - BEAST_EXPECT(graph.disconnect('a', 'b')); - BEAST_EXPECT(graph.connect('a', 'b', "repeat")); - BEAST_EXPECT(graph.connected('a', 'b')); - BEAST_EXPECT( - *graph.edge('a', 'b') == "repeat"); // NOLINT(bugprone-unchecked-optional-access) - - BEAST_EXPECT(graph.connect('a', 'c', "tree")); - - { - std::vector> edges; - - for (auto const& edge : graph.outEdges('a')) - { - edges.emplace_back(edge.source, edge.target, edge.data); - } - - std::vector> expected; - expected.emplace_back('a', 'b', "repeat"); - expected.emplace_back('a', 'c', "tree"); - BEAST_EXPECT(edges == expected); - BEAST_EXPECT(graph.outDegree('a') == expected.size()); - } - - BEAST_EXPECT(graph.outEdges('r').size() == 0); - BEAST_EXPECT(graph.outDegree('r') == 0); - BEAST_EXPECT(graph.outDegree('c') == 0); - - // only 'a' has out edges - BEAST_EXPECT(graph.outVertices().size() == 1); - std::vector const expected = {'b', 'c'}; - - BEAST_EXPECT((graph.outVertices('a') == expected)); - BEAST_EXPECT(graph.outVertices('b').size() == 0); - BEAST_EXPECT(graph.outVertices('c').size() == 0); - BEAST_EXPECT(graph.outVertices('r').size() == 0); - - std::stringstream ss; - graph.saveDot(ss, [](char v) { return v; }); - std::string const expectedDot = - "digraph {\n" - "a -> b;\n" - "a -> c;\n" - "}\n"; - BEAST_EXPECT(ss.str() == expectedDot); - } -}; - -BEAST_DEFINE_TESTSUITE(Digraph, csf, xrpl); - -} // namespace xrpl::test diff --git a/src/test/csf/Histogram_test.cpp b/src/test/csf/Histogram_test.cpp deleted file mode 100644 index 65edb14e5d..0000000000 --- a/src/test/csf/Histogram_test.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#include - -namespace xrpl::test { - -class Histogram_test : public beast::unit_test::Suite -{ -public: - void - run() override - { - using namespace csf; - Histogram hist; - - BEAST_EXPECT(hist.size() == 0); - BEAST_EXPECT(hist.numBins() == 0); - BEAST_EXPECT(hist.minValue() == 0); - BEAST_EXPECT(hist.maxValue() == 0); - BEAST_EXPECT(hist.avg() == 0); - BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue()); - BEAST_EXPECT(hist.percentile(0.5f) == 0); - BEAST_EXPECT(hist.percentile(0.9f) == 0); - BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue()); - - hist.insert(1); - - BEAST_EXPECT(hist.size() == 1); - BEAST_EXPECT(hist.numBins() == 1); - BEAST_EXPECT(hist.minValue() == 1); - BEAST_EXPECT(hist.maxValue() == 1); - BEAST_EXPECT(hist.avg() == 1); - BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue()); - BEAST_EXPECT(hist.percentile(0.5f) == 1); - BEAST_EXPECT(hist.percentile(0.9f) == 1); - BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue()); - - hist.insert(9); - - BEAST_EXPECT(hist.size() == 2); - BEAST_EXPECT(hist.numBins() == 2); - BEAST_EXPECT(hist.minValue() == 1); - BEAST_EXPECT(hist.maxValue() == 9); - BEAST_EXPECT(hist.avg() == 5); - BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue()); - BEAST_EXPECT(hist.percentile(0.5f) == 1); - BEAST_EXPECT(hist.percentile(0.9f) == 9); - BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue()); - - hist.insert(1); - - BEAST_EXPECT(hist.size() == 3); - BEAST_EXPECT(hist.numBins() == 2); - BEAST_EXPECT(hist.minValue() == 1); - BEAST_EXPECT(hist.maxValue() == 9); - BEAST_EXPECT(hist.avg() == 11 / 3); - BEAST_EXPECT(hist.percentile(0.0f) == hist.minValue()); - BEAST_EXPECT(hist.percentile(0.5f) == 1); - BEAST_EXPECT(hist.percentile(0.9f) == 9); - BEAST_EXPECT(hist.percentile(1.0f) == hist.maxValue()); - } -}; - -BEAST_DEFINE_TESTSUITE(Histogram, csf, xrpl); - -} // namespace xrpl::test diff --git a/src/test/csf/Scheduler_test.cpp b/src/test/csf/Scheduler_test.cpp deleted file mode 100644 index 6f4ac051c4..0000000000 --- a/src/test/csf/Scheduler_test.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include - -#include - -#include - -namespace xrpl::test { - -class Scheduler_test : public beast::unit_test::Suite -{ -public: - void - run() override - { - using namespace std::chrono_literals; - csf::Scheduler scheduler; - std::set seen; - - scheduler.in(1s, [&] { seen.insert(1); }); - scheduler.in(2s, [&] { seen.insert(2); }); - auto token = scheduler.in(3s, [&] { seen.insert(3); }); - scheduler.at(scheduler.now() + 4s, [&] { seen.insert(4); }); - scheduler.at(scheduler.now() + 8s, [&] { seen.insert(8); }); - - auto start = scheduler.now(); - - // Process first event - BEAST_EXPECT(seen.empty()); - BEAST_EXPECT(scheduler.stepOne()); - BEAST_EXPECT(seen == std::set({1})); - BEAST_EXPECT(scheduler.now() == (start + 1s)); - - // No processing if stepping until current time - BEAST_EXPECT(scheduler.stepUntil(scheduler.now())); - BEAST_EXPECT(seen == std::set({1})); - BEAST_EXPECT(scheduler.now() == (start + 1s)); - - // Process next event - BEAST_EXPECT(scheduler.stepFor(1s)); - BEAST_EXPECT(seen == std::set({1, 2})); - BEAST_EXPECT(scheduler.now() == (start + 2s)); - - // Don't process cancelled event, but advance clock - scheduler.cancel(token); - BEAST_EXPECT(scheduler.stepFor(1s)); - BEAST_EXPECT(seen == std::set({1, 2})); - BEAST_EXPECT(scheduler.now() == (start + 3s)); - - // Process until 3 seen ints - BEAST_EXPECT(scheduler.stepWhile([&]() { return seen.size() < 3; })); - BEAST_EXPECT(seen == std::set({1, 2, 4})); - BEAST_EXPECT(scheduler.now() == (start + 4s)); - - // Process the rest - BEAST_EXPECT(scheduler.step()); - BEAST_EXPECT(seen == std::set({1, 2, 4, 8})); - BEAST_EXPECT(scheduler.now() == (start + 8s)); - - // Process the rest again doesn't advance - BEAST_EXPECT(!scheduler.step()); - BEAST_EXPECT(seen == std::set({1, 2, 4, 8})); - BEAST_EXPECT(scheduler.now() == (start + 8s)); - } -}; - -BEAST_DEFINE_TESTSUITE(Scheduler, csf, xrpl); - -} // namespace xrpl::test diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index 2e131b895e..4828e03815 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -27,6 +27,7 @@ target_link_libraries(xrpl_tests PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl) # supported on Windows. set(test_modules basics + consensus crypto json peerfinder @@ -58,6 +59,16 @@ foreach(module IN LISTS test_modules) ) endforeach() +# The consensus tests use the CSF (Consensus Simulation Framework) helpers, so +# compile the CSF sources into the test binary. The consensus engine itself now +# lives in libxrpl, so no xrpld sources or include paths are needed here. +file( + GLOB_RECURSE csf_sources + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/csf/*.cpp" +) +target_sources(xrpl_tests PRIVATE ${csf_sources}) + # The test helpers and per-module test headers are not built with add_module, # so verify them against the test binary's own compile environment. if(verify_headers) diff --git a/src/tests/libxrpl/basics/base_uint_test.cpp b/src/tests/libxrpl/basics/base_uint.cpp similarity index 99% rename from src/tests/libxrpl/basics/base_uint_test.cpp rename to src/tests/libxrpl/basics/base_uint.cpp index c9bfc35c94..174cc33aa0 100644 --- a/src/tests/libxrpl/basics/base_uint_test.cpp +++ b/src/tests/libxrpl/basics/base_uint.cpp @@ -1,5 +1,6 @@ -#include #include + +#include #include #include diff --git a/src/tests/libxrpl/consensus/ByzantineFailureSim.cpp b/src/tests/libxrpl/consensus/ByzantineFailureSim.cpp new file mode 100644 index 0000000000..e712817121 --- /dev/null +++ b/src/tests/libxrpl/consensus/ByzantineFailureSim.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(ByzantineFailureSimTest, DISABLED_byzantine_failure_sim) +{ + using namespace csf; + using namespace std::chrono; + + // This test simulates a specific topology with nodes generating + // different ledgers due to a simulated byzantine failure (injecting + // an extra non-consensus transaction). + + Sim sim; + ConsensusParms const parms{}; + + SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); + PeerGroup a = sim.createGroup(1); + PeerGroup b = sim.createGroup(1); + PeerGroup c = sim.createGroup(1); + PeerGroup d = sim.createGroup(1); + PeerGroup e = sim.createGroup(1); + PeerGroup f = sim.createGroup(1); + PeerGroup g = sim.createGroup(1); + + a.trustAndConnect(a + b + c + g, delay); + b.trustAndConnect(b + a + c + d + e, delay); + c.trustAndConnect(c + a + b + d + e, delay); + d.trustAndConnect(d + b + c + e + f, delay); + e.trustAndConnect(e + b + c + d + f, delay); + f.trustAndConnect(f + d + e + g, delay); + g.trustAndConnect(g + a + f, delay); + + PeerGroup const network = a + b + c + d + e + f + g; + + StreamCollector sc{std::cout}; + + sim.collectors.add(sc); + + for (TrustGraph::ForkInfo const& fi : sim.trustGraph.forkablePairs(0.8)) + { + std::cout << "Can fork " << PeerGroup{fi.unlA} << " " + << " " << PeerGroup{fi.unlB} << " overlap " << fi.overlap << " required " + << fi.required << "\n"; + }; + + // set prior state + sim.run(1); + + PeerGroup byzantineNodes = a + b + c + g; + // All peers see some TX 0 + for (Peer* peer : network) + { + peer->submit(Tx{0}); + // Peers 0,1,2,6 will close the next ledger differently by injecting + // a non-consensus approved transaction + if (byzantineNodes.contains(peer)) + { + peer->txInjections.emplace(peer->lastClosedLedger.seq(), Tx{42}); + } + } + sim.run(4); + std::cout << "Branches: " << sim.branches() << "\n"; + std::cout << "Fully synchronized: " << std::boolalpha << sim.synchronized() << "\n"; + // Not tessting anything currently. + SUCCEED(); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/consensus/CensorshipDetector.cpp b/src/tests/libxrpl/consensus/CensorshipDetector.cpp new file mode 100644 index 0000000000..aa6b2d086b --- /dev/null +++ b/src/tests/libxrpl/consensus/CensorshipDetector.cpp @@ -0,0 +1,81 @@ +#include + +#include + +#include +#include +#include + +namespace xrpl::test { + +namespace { + +void +runRound( + CensorshipDetector& cdet, + int round, + std::vector proposed, + std::vector accepted, + std::vector remain, + std::vector remove) +{ + // Begin tracking what we're proposing this round + CensorshipDetector::TxIDSeqVec proposal; + for (auto const& i : proposed) + proposal.emplace_back(i, round); + cdet.propose(std::move(proposal)); + + // Finalize the round, by processing what we accepted; then + // remove anything that needs to be removed and ensure that + // what remains is correct. + cdet.check(std::move(accepted), [&remove, &remain](auto id, auto seq) { + // If the item is supposed to be removed from the censorship + // detector internal tracker manually, do it now: + if (std::ranges::find(remove, id) != remove.end()) + return true; + + // If the item is supposed to still remain in the censorship + // detector internal tracker; remove it from the vector. + auto it = std::ranges::find(remain, id); + if (it != remain.end()) + remain.erase(it); + return false; + }); + + // On entry, this set contained all the elements that should be tracked + // by the detector after we process this round. We removed all the items + // that actually were in the tracker, so this should now be empty: + EXPECT_TRUE(remain.empty()); +} + +} // namespace + +TEST(CensorshipDetectorTest, censorship_detector) +{ + SCOPED_TRACE("Censorship Detector"); + + CensorshipDetector cdet; + int round = 0; + // proposed accepted remain remove + runRound(cdet, ++round, {}, {}, {}, {}); + runRound(cdet, ++round, {10, 11, 12, 13}, {11, 2}, {10, 13}, {}); + runRound(cdet, ++round, {10, 13, 14, 15}, {14}, {10, 13, 15}, {}); + runRound(cdet, ++round, {10, 13, 15, 16}, {15, 16}, {10, 13}, {}); + runRound(cdet, ++round, {10, 13}, {17, 18}, {10, 13}, {}); + runRound(cdet, ++round, {10, 19}, {}, {10, 19}, {}); + runRound(cdet, ++round, {10, 19, 20}, {20}, {10}, {19}); + runRound(cdet, ++round, {21}, {21}, {}, {}); + runRound(cdet, ++round, {}, {22}, {}, {}); + runRound(cdet, ++round, {23, 24, 25, 26}, {25, 27}, {23, 26}, {24}); + runRound(cdet, ++round, {23, 26, 28}, {26, 28}, {23}, {}); + + for (int i = 0; i != 10; ++i) + runRound(cdet, ++round, {23}, {}, {23}, {}); + + runRound(cdet, ++round, {23, 29}, {29}, {23}, {}); + runRound(cdet, ++round, {30, 31}, {31}, {30}, {}); + runRound(cdet, ++round, {30}, {30}, {}, {}); + runRound(cdet, ++round, {}, {}, {}, {}); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/consensus/Consensus.cpp b/src/tests/libxrpl/consensus/Consensus.cpp new file mode 100644 index 0000000000..d303d28e89 --- /dev/null +++ b/src/tests/libxrpl/consensus/Consensus.cpp @@ -0,0 +1,1455 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { + +namespace { + +beast::Journal +journal() +{ + return beast::Journal{TestSink::instance()}; +} + +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, + std::unique_ptr const& clog = {}) +{ + return xrpl::shouldCloseLedger( + anyTransactions, + prevProposers, + proposersClosed, + proposersValidated, + prevRoundTime, + timeSincePrevClose, + openTime, + idleInterval, + parms, + journal(), + clog); +} + +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, + std::unique_ptr const& clog = {}) +{ + return xrpl::checkConsensus( + prevProposers, + currentProposers, + currentAgree, + currentFinished, + previousAgreeTime, + currentAgreeTime, + stalled, + parms, + proposing, + journal(), + clog); +} + +using CsfDisputedTx = DisputedTx; + +CsfDisputedTx +makeDisputedTx(csf::Tx tx, bool ourVote, std::size_t numPeers) +{ + return CsfDisputedTx{tx, ourVote, numPeers, journal()}; +} + +bool +isStalled( + CsfDisputedTx const& dispute, + ConsensusParms const& parms, + bool proposing, + int peersUnchanged, + std::unique_ptr const& clog) +{ + return dispute.stalled(parms, proposing, peersUnchanged, journal(), clog); +} + +// Helper collector for testPreferredByBranch +// Invasively disconnects network at bad times to cause splits +struct Disruptor +{ + csf::PeerGroup& network; + csf::PeerGroup& groupCfast; + csf::PeerGroup& groupCsplit; + csf::SimDuration delay; + bool reconnected = false; + + Disruptor(csf::PeerGroup& net, csf::PeerGroup& c, csf::PeerGroup& split, csf::SimDuration d) + : network(net), groupCfast(c), groupCsplit(split), delay(d) + { + } + + template + void + on(csf::PeerID, csf::SimTime, E const&) + { + } + + void + on(csf::PeerID who, csf::SimTime, csf::FullyValidateLedger const& e) + { + using namespace std::chrono; + // As soon as the fastC node fully validates C, disconnect + // ALL c nodes from the network. The fast C node needs to disconnect + // as well to prevent it from relaying the validations it did see + if (who == groupCfast[0]->id && e.ledger.seq() == csf::Ledger::Seq{2}) + { + network.disconnect(groupCsplit); + network.disconnect(groupCfast); + } + } + + void + on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e) + { + // As soon as anyone generates a child of B or C, reconnect the + // network so those validations make it through + if (!reconnected && e.ledger.seq() == csf::Ledger::Seq{3}) + { + reconnected = true; + network.connect(groupCsplit, delay); + } + } +}; + +// Helper collector for testPauseForLaggards +// This will remove the ledgerAccept delay used to +// initially create the slow vs. fast validator groups. +struct UndoDelay +{ + csf::PeerGroup& g; + + UndoDelay(csf::PeerGroup& a) : g(a) + { + } + + template + void + on(csf::PeerID, csf::SimTime, E const&) + { + } + + void + on(csf::PeerID who, csf::SimTime, csf::AcceptLedger const& e) + { + for (csf::Peer* p : g) + { + if (p->id == who) + p->delays.ledgerAccept = std::chrono::seconds{0}; + } + } +}; + +} // namespace + +TEST(ConsensusTest, should_close_ledger) +{ + using namespace std::chrono_literals; + SCOPED_TRACE("should close ledger"); + + // Use default parameters + ConsensusParms const p{}; + + // Bizarre times forcibly close + EXPECT_TRUE(shouldCloseLedger(true, 10, 10, 10, -10s, 10s, 1s, 1s, p)); + EXPECT_TRUE(shouldCloseLedger(true, 10, 10, 10, 100h, 10s, 1s, 1s, p)); + EXPECT_TRUE(shouldCloseLedger(true, 10, 10, 10, 10s, 100h, 1s, 1s, p)); + + // Rest of network has closed + EXPECT_TRUE(shouldCloseLedger(true, 10, 3, 5, 10s, 10s, 10s, 10s, p)); + + // No transactions means wait until end of internval + EXPECT_TRUE(!shouldCloseLedger(false, 10, 0, 0, 1s, 1s, 1s, 10s, p)); + EXPECT_TRUE(shouldCloseLedger(false, 10, 0, 0, 1s, 10s, 1s, 10s, p)); + + // Enforce minimum ledger open time + EXPECT_TRUE(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 1s, 10s, p)); + + // Don't go too much faster than last time + EXPECT_TRUE(!shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 3s, 10s, p)); + + EXPECT_TRUE(shouldCloseLedger(true, 10, 0, 0, 10s, 10s, 10s, 10s, p)); +} + +TEST(ConsensusTest, check_consensus) +{ + using namespace std::chrono_literals; + SCOPED_TRACE("check consensus"); + + // Use default parameters + ConsensusParms const p{}; + + /////////////// + // Disputes still in doubt + // + // Not enough time has elapsed + EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, false, p, true)); + + // If not enough peers have proposed, ensure + // more time for proposals + EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 4s, false, p, true)); + + // Enough time has elapsed and we all agree + EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 2, 0, 3s, 10s, false, p, true)); + + // Enough time has elapsed and we don't yet agree + EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 1, 0, 3s, 10s, false, p, true)); + + // Our peers have moved on + // Enough time has elapsed and we all agree + EXPECT_TRUE(ConsensusState::MovedOn == checkConsensus(10, 2, 1, 8, 3s, 10s, false, p, true)); + + // If no peers, don't agree until time has passed. + EXPECT_TRUE(ConsensusState::No == checkConsensus(0, 0, 0, 0, 3s, 10s, false, p, true)); + + // Agree if no peers and enough time has passed. + EXPECT_TRUE(ConsensusState::Yes == checkConsensus(0, 0, 0, 0, 3s, 16s, false, p, true)); + + // Expire if too much time has passed without agreement + EXPECT_TRUE(ConsensusState::Expired == checkConsensus(10, 8, 1, 0, 1s, 19s, false, p, true)); + + /////////////// + // Stalled + // + // Not enough time has elapsed + EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, true, p, true)); + + // If not enough peers have proposed, ensure + // more time for proposals + EXPECT_TRUE(ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 4s, true, p, true)); + + // Enough time has elapsed and we all agree + EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 2, 0, 3s, 10s, true, p, true)); + + // Enough time has elapsed and we don't yet agree, but there's nothing + // left to dispute + EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 1, 0, 3s, 10s, true, p, true)); + + // Our peers have moved on + // Enough time has elapsed and we all agree, nothing left to dispute + EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 2, 1, 8, 3s, 10s, true, p, true)); + + // If no peers, don't agree until time has passed. + EXPECT_TRUE(ConsensusState::No == checkConsensus(0, 0, 0, 0, 3s, 10s, true, p, true)); + + // Agree if no peers and enough time has passed. + EXPECT_TRUE(ConsensusState::Yes == checkConsensus(0, 0, 0, 0, 3s, 16s, true, p, true)); + + // We are done if there's nothing left to dispute, no matter how much + // time has passed + EXPECT_TRUE(ConsensusState::Yes == checkConsensus(10, 8, 1, 0, 1s, 19s, true, p, true)); +} + +TEST(ConsensusTest, standalone) +{ + using namespace std::chrono_literals; + using namespace csf; + SCOPED_TRACE("standalone"); + + Sim s; + PeerGroup const peers = s.createGroup(1); + Peer* peer = peers[0]; + peer->targetLedgers = 1; + peer->start(); + peer->submit(Tx{1}); + + s.scheduler.step(); + + // Inspect that the proper ledger was created + auto const& lcl = peer->lastClosedLedger; + EXPECT_TRUE(peer->prevLedgerID() == lcl.id()); + EXPECT_TRUE(lcl.seq() == Ledger::Seq{1}); + EXPECT_TRUE(lcl.txs().size() == 1); + EXPECT_TRUE(lcl.txs().contains(Tx{1})); + EXPECT_TRUE(peer->prevProposers == 0); +} + +TEST(ConsensusTest, peers_agree) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("peers agree"); + + ConsensusParms const parms{}; + Sim sim; + PeerGroup peers = sim.createGroup(5); + + // Connected trust and network graphs with single fixed delay + peers.trustAndConnect(peers, round(0.2 * parms.ledgerGRANULARITY)); + + // everyone submits their own ID as a TX + for (Peer* p : peers) + p->submit(Tx(static_cast(p->id))); + + sim.run(1); + + // All peers are in sync + EXPECT_TRUE(sim.synchronized()); + if (sim.synchronized()) + { + for (Peer const* peer : peers) + { + auto const& lcl = peer->lastClosedLedger; + EXPECT_TRUE(lcl.id() == peer->prevLedgerID()); + EXPECT_TRUE(lcl.seq() == Ledger::Seq{1}); + // All peers proposed + EXPECT_TRUE(peer->prevProposers == peers.size() - 1); + // All transactions were accepted + for (std::uint32_t i = 0; i < peers.size(); ++i) + EXPECT_TRUE(lcl.txs().contains(Tx{i})); + } + } +} + +TEST(ConsensusTest, slow_peers) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("slow peers"); + + // Several tests of a complete trust graph with a subset of peers + // that have significantly longer network delays to the rest of the + // network + + // Test when a slow peer doesn't delay a consensus quorum (4/5 agree) + { + ConsensusParms const parms{}; + Sim sim; + PeerGroup slow = sim.createGroup(1); + PeerGroup fast = sim.createGroup(4); + PeerGroup network = fast + slow; + + // Fully connected trust graph + network.trust(network); + + // Fast and slow network connections + fast.connect(fast, round(0.2 * parms.ledgerGRANULARITY)); + + slow.connect(network, round(1.1 * parms.ledgerGRANULARITY)); + + // All peers submit their own ID as a transaction + for (Peer* peer : network) + peer->submit(Tx{static_cast(peer->id)}); + + sim.run(1); + + // Verify all peers have same LCL but are missing transaction 0 + // All peers are in sync even with a slower peer 0 + EXPECT_TRUE(sim.synchronized()); + if (sim.synchronized()) + { + for (Peer const* peer : network) + { + auto const& lcl = peer->lastClosedLedger; + EXPECT_TRUE(lcl.id() == peer->prevLedgerID()); + EXPECT_TRUE(lcl.seq() == Ledger::Seq{1}); + + EXPECT_TRUE(peer->prevProposers == network.size() - 1); + EXPECT_TRUE(peer->prevRoundTime == network[0]->prevRoundTime); + + EXPECT_TRUE(not lcl.txs().contains(Tx{0})); + for (std::uint32_t i = 2; i < network.size(); ++i) + EXPECT_TRUE(lcl.txs().contains(Tx{i})); + + // Tx 0 didn't make it + EXPECT_TRUE(peer->openTxs.contains(Tx{0})); + } + } + } + + // Test when the slow peers delay a consensus quorum (4/6 agree) + { + // Run two tests + // 1. The slow peers are participating in consensus + // 2. The slow peers are just observing + + for (auto isParticipant : {true, false}) + { + ConsensusParms const parms{}; + + Sim sim; + PeerGroup slow = sim.createGroup(2); + PeerGroup fast = sim.createGroup(4); + PeerGroup network = fast + slow; + + // Connected trust graph + network.trust(network); + + // Fast and slow network connections + fast.connect(fast, round(0.2 * parms.ledgerGRANULARITY)); + + slow.connect(network, round(1.1 * parms.ledgerGRANULARITY)); + + for (Peer* peer : slow) + peer->runAsValidator = isParticipant; + + // All peers submit their own ID as a transaction and relay it + // to peers + for (Peer* peer : network) + peer->submit(Tx{static_cast(peer->id)}); + + sim.run(1); + + EXPECT_TRUE(sim.synchronized()); + if (sim.synchronized()) + { + // Verify all peers have same LCL but are missing + // transaction 0,1 which was not received by all peers + // before the ledger closed + for (Peer const* peer : network) + { + // Closed ledger has all but transaction 0,1 + auto const& lcl = peer->lastClosedLedger; + EXPECT_TRUE(lcl.seq() == Ledger::Seq{1}); + EXPECT_TRUE(not lcl.txs().contains(Tx{0})); + EXPECT_TRUE(not lcl.txs().contains(Tx{1})); + for (std::uint32_t i = slow.size(); i < network.size(); ++i) + EXPECT_TRUE(lcl.txs().contains(Tx{i})); + + // Tx 0-1 didn't make it + EXPECT_TRUE(peer->openTxs.contains(Tx{0})); + EXPECT_TRUE(peer->openTxs.contains(Tx{1})); + } + + Peer const* slowPeer = slow[0]; + if (isParticipant) + { + EXPECT_TRUE(slowPeer->prevProposers == network.size() - 1); + } + else + { + EXPECT_TRUE(slowPeer->prevProposers == fast.size()); + } + + for (Peer const* peer : fast) + { + // Due to the network link delay settings + // Peer 0 initially proposes {0} + // Peer 1 initially proposes {1} + // Peers 2-5 initially propose {2,3,4,5} + // Since peers 2-5 agree, 4/6 > the initial 50% needed + // to include a disputed transaction, so Peer 0/1 switch + // to agree with those peers. Peer 0/1 then closes with + // an 80% quorum of agreeing positions (5/6) match. + // + // Peers 2-5 do not change position, since tx 0 or tx 1 + // have less than the 50% initial threshold. They also + // cannot declare consensus, since 4/6 agreeing + // positions are < 80% threshold. They therefore need an + // additional timerEntry call to see the updated + // positions from Peer 0 & 1. + + if (isParticipant) + { + EXPECT_TRUE(peer->prevProposers == network.size() - 1); + EXPECT_TRUE(peer->prevRoundTime > slowPeer->prevRoundTime); + } + else + { + EXPECT_TRUE(peer->prevProposers == fast.size() - 1); + // so all peers should have closed together + EXPECT_TRUE(peer->prevRoundTime == slowPeer->prevRoundTime); + } + } + } + } + } +} + +TEST(ConsensusTest, close_time_disagree) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("close time disagree"); + + // This is a very specialized test to get ledgers to disagree on + // the close time. It unfortunately assumes knowledge about current + // timing constants. This is a necessary evil to get coverage up + // pending more extensive refactorings of timing constants. + + // In order to agree-to-disagree on the close time, there must be no + // clear majority of nodes agreeing on a close time. This test + // sets a relative offset to the peers internal clocks so that they + // send proposals with differing times. + + // However, agreement is on the effective close time, not the + // exact close time. The minimum closeTimeResolution is given by + // ledgerPossibleTimeResolutions[0], which is currently 10s. This means + // the skews need to be at least 10 seconds to have different effective + // close times. + + // Complicating this matter is that nodes will ignore proposals + // with times more than proposeFRESHNESS =20s in the past. So at + // the minimum granularity, we have at most 3 types of skews + // (0s,10s,20s). + + // This test therefore has 6 nodes, with 2 nodes having each type of + // skew. Then no majority (1/3 < 1/2) of nodes will agree on an + // actual close time. + + ConsensusParms const parms{}; + Sim sim; + + PeerGroup groupA = sim.createGroup(2); + PeerGroup const groupB = sim.createGroup(2); + PeerGroup const groupC = sim.createGroup(2); + PeerGroup network = groupA + groupB + groupC; + + network.trust(network); + network.connect(network, round(0.2 * parms.ledgerGRANULARITY)); + + // Run consensus without skew until we have a short close time + // resolution + Peer const* firstPeer = *groupA.begin(); + while (firstPeer->lastClosedLedger.closeTimeResolution() >= parms.proposeFRESHNESS) + sim.run(1); + + // Introduce a shift on the time of 2/3 of peers + for (Peer* peer : groupA) + peer->clockSkew = parms.proposeFRESHNESS / 2; + for (Peer* peer : groupB) + peer->clockSkew = parms.proposeFRESHNESS; + + sim.run(1); + + // All nodes agreed to disagree on the close time + EXPECT_TRUE(sim.synchronized()); + if (sim.synchronized()) + { + for (Peer const* peer : network) + EXPECT_TRUE(!peer->lastClosedLedger.closeAgree()); + } +} + +TEST(ConsensusTest, wrong_lcl) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("wrong LCL"); + + // Specialized test to exercise a temporary fork in which some peers + // are working on an incorrect prior ledger. + + ConsensusParms const parms{}; + + // Vary the time it takes to process validations to exercise detecting + // the wrong LCL at different phases of consensus + for (auto validationDelay : {0ms, parms.ledgerMinClose}) + { + // Consider 10 peers: + // 0 1 2 3 4 5 6 7 8 9 + // minority majorityA majorityB + // + // Nodes 0-1 trust nodes 0-4 + // Nodes 2-9 trust nodes 2-9 + // + // By submitting tx 0 to nodes 0-4 and tx 1 to nodes 5-9, + // nodes 0-1 will generate the wrong LCL (with tx 0). The remaining + // nodes will instead accept the ledger with tx 1. + + // Nodes 0-1 will detect this mismatch during a subsequent round + // since nodes 2-4 will validate a different ledger. + + // Nodes 0-1 will acquire the proper ledger from the network and + // resume consensus and eventually generate the dominant network + // ledger. + + // This topology can potentially fork with the above trust relations + // but that is intended for this test. + + Sim sim; + + PeerGroup minority = sim.createGroup(2); + PeerGroup const majorityA = sim.createGroup(3); + PeerGroup const majorityB = sim.createGroup(5); + + PeerGroup majority = majorityA + majorityB; + PeerGroup const network = minority + majority; + + SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); + minority.trustAndConnect(minority + majorityA, delay); + majority.trustAndConnect(majority, delay); + + CollectByNode jumps; + sim.collectors.add(jumps); + + EXPECT_TRUE(sim.trustGraph.canFork(parms.minConsensusPct / 100.)); + + // initial round to set prior state + sim.run(1); + + // Nodes in smaller UNL have seen tx 0, nodes in other unl have seen + // tx 1 + for (Peer* peer : network) + peer->delays.recvValidation = validationDelay; + for (Peer* peer : (minority + majorityA)) + peer->openTxs.insert(Tx{0}); + for (Peer* peer : majorityB) + peer->openTxs.insert(Tx{1}); + + // Run for additional rounds + // With no validation delay, only 2 more rounds are needed. + // 1. Round to generate different ledgers + // 2. Round to detect different prior ledgers (but still generate + // wrong ones) and recover within that round since wrong LCL + // is detected before we close + // + // With a validation delay of ledgerMinClose, we need 3 more + // rounds. + // 1. Round to generate different ledgers + // 2. Round to detect different prior ledgers (but still generate + // wrong ones) but end up declaring consensus on wrong LCL (but + // with the right transaction set!). This is because we detect + // the wrong LCL after we have closed the ledger, so we declare + // consensus based solely on our peer proposals. But we haven't + // had time to acquire the right ledger. + // 3. Round to correct + sim.run(3); + + // The network never actually forks, since node 0-1 never see a + // quorum of validations to fully validate the incorrect chain. + + // However, for a non zero-validation delay, the network is not + // synchronized because nodes 0 and 1 are running one ledger behind + EXPECT_TRUE(sim.branches() == 1); + if (sim.branches() == 1) + { + for (Peer const* peer : majority) + { + // No jumps for majority nodes + EXPECT_TRUE(jumps[peer->id].closeJumps.empty()); + EXPECT_TRUE(jumps[peer->id].fullyValidatedJumps.empty()); + } + for (Peer const* peer : minority) + { + auto& peerJumps = jumps[peer->id]; + // last closed ledger jump between chains + { + EXPECT_TRUE(peerJumps.closeJumps.size() == 1); + if (peerJumps.closeJumps.size() == 1) + { + JumpCollector::Jump const& jump = peerJumps.closeJumps.front(); + // Jump is to a different chain + EXPECT_TRUE(jump.from.seq() <= jump.to.seq()); + EXPECT_TRUE(!jump.to.isAncestor(jump.from)); + } + } + // fully validated jump forward in same chain + { + EXPECT_TRUE(peerJumps.fullyValidatedJumps.size() == 1); + if (peerJumps.fullyValidatedJumps.size() == 1) + { + JumpCollector::Jump const& jump = peerJumps.fullyValidatedJumps.front(); + // Jump is to a different chain with same seq + EXPECT_TRUE(jump.from.seq() < jump.to.seq()); + EXPECT_TRUE(jump.to.isAncestor(jump.from)); + } + } + } + } + } + + { + // Additional test engineered to switch LCL during the establish + // phase. This was added to trigger a scenario that previously + // crashed, in which switchLCL switched from establish to open + // phase, but still processed the establish phase logic. + + // Loner node will accept an initial ledger A, but all other nodes + // accept ledger B a bit later. By delaying the time it takes + // to process a validation, loner node will detect the wrongLCL + // after it is already in the establish phase of the next round. + + Sim sim; + PeerGroup loner = sim.createGroup(1); + PeerGroup const friends = sim.createGroup(3); + loner.trust(loner + friends); + + PeerGroup const others = sim.createGroup(6); + PeerGroup clique = friends + others; + clique.trust(clique); + + PeerGroup network = loner + clique; + network.connect(network, round(0.2 * parms.ledgerGRANULARITY)); + + // initial round to set prior state + sim.run(1); + for (Peer* peer : (loner + friends)) + peer->openTxs.insert(Tx(0)); + for (Peer* peer : others) + peer->openTxs.insert(Tx(1)); + + // Delay validation processing + for (Peer* peer : network) + peer->delays.recvValidation = parms.ledgerGRANULARITY; + + // additional rounds to generate wrongLCL and recover + sim.run(2); + + // Check all peers recovered + for (Peer const* p : network) + EXPECT_TRUE(p->prevLedgerID() == network[0]->prevLedgerID()); + } +} + +TEST(ConsensusTest, consensus_close_time_rounding) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("consensus close time rounding"); + + // This is a specialized test engineered to yield ledgers with different + // close times even though the peers believe they had close time + // consensus on the ledger. + ConsensusParms const parms; + + Sim sim; + + // This requires a group of 4 fast and 2 slow peers to create a + // situation in which a subset of peers requires seeing additional + // proposals to declare consensus. + PeerGroup slow = sim.createGroup(2); + PeerGroup fast = sim.createGroup(4); + PeerGroup network = fast + slow; + + // Connected trust graph + network.trust(network); + + // Fast and slow network connections + fast.connect(fast, round(0.2 * parms.ledgerGRANULARITY)); + slow.connect(network, round(1.1 * parms.ledgerGRANULARITY)); + + // Run to the ledger *prior* to decreasing the resolution + sim.run(kIncreaseLedgerTimeResolutionEvery - 2); + + // In order to create the discrepancy, we want a case where if + // X = effCloseTime(closeTime, resolution, parentCloseTime) + // X != effCloseTime(X, resolution, parentCloseTime) + // + // That is, the effective close time is not a fixed point. This can + // happen if X = parentCloseTime + 1, but a subsequent rounding goes + // to the next highest multiple of resolution. + + // So we want to find an offset (now + offset) % 30s = 15 + // (now + offset) % 20s = 15 + // This way, the next ledger will close and round up Due to the + // network delay settings, the round of consensus will take 5s, so + // the next ledger's close time will + + NetClock::duration when = network[0]->now().time_since_epoch(); + + // Check we are before the 30s to 20s transition + NetClock::duration const resolution = network[0]->lastClosedLedger.closeTimeResolution(); + EXPECT_TRUE(resolution == NetClock::duration{30s}); + + while (((when % NetClock::duration{30s}) != NetClock::duration{15s}) || + ((when % NetClock::duration{20s}) != NetClock::duration{15s})) + when += 1s; + // Advance the clock without consensus running (IS THIS WHAT + // PREVENTS IT IN PRACTICE?) + sim.scheduler.stepFor(NetClock::time_point{when} - network[0]->now()); + + // Run one more ledger with 30s resolution + sim.run(1); + EXPECT_TRUE(sim.synchronized()); + if (sim.synchronized()) + { + // close time should be ahead of clock time since we engineered + // the close time to round up + for (Peer const* peer : network) + { + EXPECT_TRUE(peer->lastClosedLedger.closeTime() > peer->now()); + EXPECT_TRUE(peer->lastClosedLedger.closeAgree()); + } + } + + // All peers submit their own ID as a transaction + for (Peer* peer : network) + peer->submit(Tx{static_cast(peer->id)}); + + // Run 1 more round, this time it will have a decreased + // resolution of 20 seconds. + + // The network delays are engineered so that the slow peers + // initially have the wrong tx hash, but they see a majority + // of agreement from their peers and declare consensus + // + // The trick is that everyone starts with a raw close time of + // 84681s + // Which has + // effCloseTime(86481s, 20s, 86490s) = 86491s + // However, when the slow peers update their position, they change + // the close time to 86451s. The fast peers declare consensus with + // the 86481s as their position still. + // + // When accepted the ledger + // - fast peers use eff(86481s) -> 86491s as the close time + // - slow peers use eff(eff(86481s)) -> eff(86491s) -> 86500s! + + sim.run(1); + + EXPECT_TRUE(sim.synchronized()); +} + +TEST(ConsensusTest, fork) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("fork"); + + std::uint32_t const numPeers = 10; + // Vary overlap between two UNLs + for (std::uint32_t overlap = 0; overlap <= numPeers; ++overlap) + { + ConsensusParms const parms{}; + Sim sim; + + std::uint32_t const numA = (numPeers - overlap) / 2; + std::uint32_t const numB = numPeers - numA - overlap; + + PeerGroup const aOnly = sim.createGroup(numA); + PeerGroup const bOnly = sim.createGroup(numB); + PeerGroup const commonOnly = sim.createGroup(overlap); + + PeerGroup a = aOnly + commonOnly; + PeerGroup b = bOnly + commonOnly; + + PeerGroup const network = a + b; + + SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); + a.trustAndConnect(a, delay); + b.trustAndConnect(b, delay); + + // Initial round to set prior state + sim.run(1); + for (Peer* peer : network) + { + // Nodes have only seen transactions from their neighbors + peer->openTxs.insert(Tx{static_cast(peer->id)}); + for (Peer const* to : sim.trustGraph.trustedPeers(peer)) + peer->openTxs.insert(Tx{static_cast(to->id)}); + } + sim.run(1); + + // Fork should not happen for 40% or greater overlap + // Since the overlapped nodes have a UNL that is the union of the + // two cliques, the maximum sized UNL list is the number of peers + if (overlap > 0.4 * numPeers) + { + EXPECT_TRUE(sim.synchronized()); + } + else + { + // Even if we do fork, there shouldn't be more than 3 ledgers + // One for cliqueA, one for cliqueB and one for nodes in both + EXPECT_TRUE(sim.branches() <= 3); + } + } +} + +TEST(ConsensusTest, hub_network) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("hub network"); + + // Simulate a set of 5 validators that aren't directly connected but + // rely on a single hub node for communication + + ConsensusParms const parms{}; + Sim sim; + PeerGroup validators = sim.createGroup(5); + PeerGroup center = sim.createGroup(1); + validators.trust(validators); + center.trust(validators); + + SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); + validators.connect(center, delay); + + center[0]->runAsValidator = false; + + // prep round to set initial state. + sim.run(1); + + // everyone submits their own ID as a TX and relay it to peers + for (Peer* p : validators) + p->submit(Tx(static_cast(p->id))); + + sim.run(1); + + // All peers are in sync + EXPECT_TRUE(sim.synchronized()); +} + +TEST(ConsensusTest, preferred_by_branch) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("preferred by branch"); + + // Simulate network splits that are prevented from forking when using + // preferred ledger by trie. This is a contrived example that involves + // excessive network splits, but demonstrates the safety improvement + // from the preferred ledger by trie approach. + + // Consider 10 validating nodes that comprise a single common UNL + // Ledger history: + // 1: A + // _/ \_ + // 2: B C + // _/ _/ \_ + // 3: D C' |||||||| (8 different ledgers) + + // - All nodes generate the common ledger A + // - 2 nodes generate B and 8 nodes generate C + // - Only 1 of the C nodes sees all the C validations and fully + // validates C. The rest of the C nodes split at just the right time + // such that they never see any C validations but their own. + // - The C nodes continue and generate 8 different child ledgers. + // - Meanwhile, the D nodes only saw 1 validation for C and 2 + // validations + // for B. + // - The network reconnects and the validations for generation 3 ledgers + // are observed (D and the 8 C's) + // - In the old approach, 2 votes for D outweighs 1 vote for each C' + // so the network would avalanche towards D and fully validate it + // EVEN though C was fully validated by one node + // - In the new approach, 2 votes for D are not enough to outweight the + // 8 implicit votes for C, so nodes will avalanche to C instead + + ConsensusParms const parms{}; + Sim sim; + + // Goes A->B->D + PeerGroup const groupABD = sim.createGroup(2); + // Single node that initially fully validates C before the split + PeerGroup groupCfast = sim.createGroup(1); + // Generates C, but fails to fully validate before the split + PeerGroup groupCsplit = sim.createGroup(7); + + PeerGroup groupNotFastC = groupABD + groupCsplit; + PeerGroup network = groupABD + groupCsplit + groupCfast; + + SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); + SimDuration const fDelay = round(0.1 * parms.ledgerGRANULARITY); + + network.trust(network); + // C must have a shorter delay to see all the validations before the + // other nodes + network.connect(groupCfast, fDelay); + // The rest of the network is connected at the same speed + groupNotFastC.connect(groupNotFastC, delay); + + Disruptor dc(network, groupCfast, groupCsplit, delay); + sim.collectors.add(dc); + + // Consensus round to generate ledger A + sim.run(1); + EXPECT_TRUE(sim.synchronized()); + + // Next round generates B and C + // To force B, we inject an extra transaction in to those nodes + for (Peer* peer : groupABD) + { + peer->txInjections.emplace(peer->lastClosedLedger.seq(), Tx{42}); + } + // The Disruptor will ensure that nodes disconnect before the C + // validations make it to all but the fastC node + sim.run(1); + + // We are no longer in sync, but have not yet forked: + // 9 nodes consider A the last fully validated ledger and fastC sees C + EXPECT_TRUE(!sim.synchronized()); + EXPECT_TRUE(sim.branches() == 1); + + // Run another round to generate the 8 different C' ledgers + for (Peer* p : network) + p->submit(Tx(static_cast(p->id))); + sim.run(1); + + // Still not forked + EXPECT_TRUE(!sim.synchronized()); + EXPECT_TRUE(sim.branches() == 1); + + // Disruptor will reconnect all but the fastC node + sim.run(1); + + EXPECT_TRUE(sim.branches() == 1); + if (sim.branches() == 1) + { + EXPECT_TRUE(sim.synchronized()); + } + else // old approach caused a fork + { + EXPECT_TRUE(sim.branches(groupNotFastC) == 1); + EXPECT_TRUE(sim.synchronized(groupNotFastC) == 1); + } +} + +TEST(ConsensusTest, pause_for_laggards) +{ + using namespace csf; + using namespace std::chrono; + SCOPED_TRACE("pause for laggards"); + + // Test that validators that jump ahead of the network slow + // down. + + // We engineer the following validated ledger history scenario: + // + // / --> B1 --> C1 --> ... -> G1 "ahead" + // A + // \ --> B2 --> C2 "behind" + // + // After validating a common ledger A, a set of "behind" validators + // briefly run slower and validate the lower chain of ledgers. + // The "ahead" validators run normal speed and run ahead validating the + // upper chain of ledgers. + // + // Due to the uncommitted support definition of the preferred branch + // protocol, even if the "behind" validators are a majority, the "ahead" + // validators cannot jump to the proper branch until the "behind" + // validators catch up to the same sequence number. For this test to + // succeed, the ahead validators need to briefly slow down consensus. + + ConsensusParms const parms{}; + Sim sim; + SimDuration const delay = round(0.2 * parms.ledgerGRANULARITY); + + PeerGroup behind = sim.createGroup(3); + PeerGroup const ahead = sim.createGroup(2); + PeerGroup network = ahead + behind; + + hash_set trustedKeys; + for (Peer const* p : network) + trustedKeys.insert(p->key); + for (Peer* p : network) + p->trustedKeys = trustedKeys; + + network.trustAndConnect(network, delay); + + // Initial seed round to set prior state + sim.run(1); + + // Have the "behind" group initially take a really long time to + // accept a ledger after ending deliberation + for (Peer* p : behind) + p->delays.ledgerAccept = 20s; + + // Use the collector to revert the delay after the single + // slow ledger is generated + UndoDelay undoDelay{behind}; + sim.collectors.add(undoDelay); + + // Run the simulation for 100 seconds of simulation time with + std::chrono::nanoseconds const simDuration = 100s; + + // Simulate clients submitting 1 tx every 5 seconds to a random + // validator + Rate const rate{.count = 1, .duration = 5s}; + auto peerSelector = makeSelector( + network.begin(), network.end(), std::vector(network.size(), 1.), sim.rng); + auto txSubmitter = makeSubmitter( + ConstantDistribution{rate.inv()}, + sim.scheduler.now(), + sim.scheduler.now() + simDuration, + peerSelector, + sim.scheduler, + sim.rng); + + // Run simulation + sim.run(simDuration); + + // Verify that the network recovered + EXPECT_TRUE(sim.synchronized()); +} + +TEST(ConsensusTest, disputes) +{ + SCOPED_TRACE("disputes"); + + using namespace csf; + + // Test dispute objects directly + using Dispute = CsfDisputedTx; + + Tx const txTrue{99}; + Tx const txFalse{98}; + Tx const txFollowingTrue{97}; + Tx const txFollowingFalse{96}; + int const numPeers = 100; + ConsensusParms const p; + std::size_t peersUnchanged = 0; + + auto clog = std::make_unique(); + + // Three cases: + // 1 proposing, initial vote yes + // 2 proposing, initial vote no + // 3 not proposing, initial vote doesn't matter after the first update, + // use yes + { + Dispute proposingTrue = makeDisputedTx(txTrue, true, numPeers); + Dispute proposingFalse = makeDisputedTx(txFalse, false, numPeers); + Dispute followingTrue = makeDisputedTx(txFollowingTrue, true, numPeers); + Dispute followingFalse = makeDisputedTx(txFollowingFalse, false, numPeers); + EXPECT_TRUE(proposingTrue.id() == 99); + EXPECT_TRUE(proposingFalse.id() == 98); + EXPECT_TRUE(followingTrue.id() == 97); + EXPECT_TRUE(followingFalse.id() == 96); + + // Create an even split in the peer votes + for (int i = 0; i < numPeers; ++i) + { + EXPECT_TRUE(proposingTrue.setVote(PeerID(i), i < 50)); + EXPECT_TRUE(proposingFalse.setVote(PeerID(i), i < 50)); + EXPECT_TRUE(followingTrue.setVote(PeerID(i), i < 50)); + EXPECT_TRUE(followingFalse.setVote(PeerID(i), i < 50)); + } + // Switch the middle vote to match mine + EXPECT_TRUE(proposingTrue.setVote(PeerID(50), true)); + EXPECT_TRUE(proposingFalse.setVote(PeerID(49), false)); + EXPECT_TRUE(followingTrue.setVote(PeerID(50), true)); + EXPECT_TRUE(followingFalse.setVote(PeerID(49), false)); + + // no changes yet + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog)); + EXPECT_TRUE(clog->str().empty()); + + // I'm in the majority, my vote should not change + EXPECT_TRUE(!proposingTrue.updateVote(5, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(5, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(5, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(5, false, p)); + + EXPECT_TRUE(!proposingTrue.updateVote(10, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(10, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(10, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(10, false, p)); + + peersUnchanged = 2; + EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog)); + EXPECT_TRUE(clog->str().empty()); + + // Right now, the vote is 51%. The requirement is about to jump to + // 65% + EXPECT_TRUE(proposingTrue.updateVote(55, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(55, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(55, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(55, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == false); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + // 16 validators change their vote to match my original vote + for (int i = 0; i < 16; ++i) + { + auto pTrue = PeerID(numPeers - i - 1); + auto pFalse = PeerID(i); + EXPECT_TRUE(proposingTrue.setVote(pTrue, true)); + EXPECT_TRUE(proposingFalse.setVote(pFalse, false)); + EXPECT_TRUE(followingTrue.setVote(pTrue, true)); + EXPECT_TRUE(followingFalse.setVote(pFalse, false)); + } + // The vote should now be 66%, threshold is 65% + EXPECT_TRUE(proposingTrue.updateVote(60, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(60, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(60, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(60, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + // Threshold jumps to 70% + EXPECT_TRUE(proposingTrue.updateVote(86, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(86, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(86, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(86, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == false); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + // 5 more validators change their vote to match my original vote + for (int i = 16; i < 21; ++i) + { + auto pTrue = PeerID(numPeers - i - 1); + auto pFalse = PeerID(i); + EXPECT_TRUE(proposingTrue.setVote(pTrue, true)); + EXPECT_TRUE(proposingFalse.setVote(pFalse, false)); + EXPECT_TRUE(followingTrue.setVote(pTrue, true)); + EXPECT_TRUE(followingFalse.setVote(pFalse, false)); + } + + // The vote should now be 71%, threshold is 70% + EXPECT_TRUE(proposingTrue.updateVote(90, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(90, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(90, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(90, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + // The vote should now be 71%, threshold is 70% + EXPECT_TRUE(!proposingTrue.updateVote(150, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(150, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(150, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(150, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + // The vote should now be 71%, threshold is 70% + EXPECT_TRUE(!proposingTrue.updateVote(190, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(190, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(190, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(190, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + peersUnchanged = 3; + EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog)); + EXPECT_TRUE(clog->str().empty()); + + // Threshold jumps to 95% + EXPECT_TRUE(proposingTrue.updateVote(220, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(220, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(220, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(220, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == false); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + // 25 more validators change their vote to match my original vote + for (int i = 21; i < 46; ++i) + { + auto pTrue = PeerID(numPeers - i - 1); + auto pFalse = PeerID(i); + EXPECT_TRUE(proposingTrue.setVote(pTrue, true)); + EXPECT_TRUE(proposingFalse.setVote(pFalse, false)); + EXPECT_TRUE(followingTrue.setVote(pTrue, true)); + EXPECT_TRUE(followingFalse.setVote(pFalse, false)); + } + + // The vote should now be 96%, threshold is 95% + EXPECT_TRUE(proposingTrue.updateVote(250, true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(250, true, p)); + EXPECT_TRUE(!followingTrue.updateVote(250, false, p)); + EXPECT_TRUE(!followingFalse.updateVote(250, false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + for (peersUnchanged = 0; peersUnchanged < 6; ++peersUnchanged) + { + EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(proposingFalse, p, true, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingTrue, p, false, peersUnchanged, clog)); + EXPECT_TRUE(!isStalled(followingFalse, p, false, peersUnchanged, clog)); + EXPECT_TRUE(clog->str().empty()); + } + + auto expectStalled = [&clog]( + int txid, + bool ourVote, + int ourTime, + int peerTime, + int support, + std::uint32_t line) { + using namespace std::string_literals; + + auto const s = clog->str(); + SCOPED_TRACE(::testing::Message() << __FILE__ << ":" << line); + EXPECT_NE(s.find("stalled"), s.npos) << s; + EXPECT_TRUE(s.starts_with("Transaction "s + std::to_string(txid))) << s; + EXPECT_NE(s.find("voting "s + (ourVote ? "YES" : "NO")), s.npos) << s; + EXPECT_NE(s.find("for "s + std::to_string(ourTime) + " rounds."s), s.npos) << s; + EXPECT_NE(s.find("votes in "s + std::to_string(peerTime) + " rounds."), s.npos) << s; + EXPECT_TRUE(s.ends_with("has "s + std::to_string(support) + "% support. "s)) << s; + clog = std::make_unique(); + }; + + for (int i = 0; i < 1; ++i) + { + EXPECT_TRUE(!proposingTrue.updateVote(250 + (10 * i), true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(250 + (10 * i), true, p)); + EXPECT_TRUE(!followingTrue.updateVote(250 + (10 * i), false, p)); + EXPECT_TRUE(!followingFalse.updateVote(250 + (10 * i), false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + // true vote has changed recently, so not stalled + EXPECT_TRUE(!isStalled(proposingTrue, p, true, 0, clog)); + EXPECT_TRUE(clog->str().empty()); + // remaining votes have been unchanged in so long that we only + // need to hit the second round at 95% to be stalled, regardless + // of peers + EXPECT_TRUE(isStalled(proposingFalse, p, true, 0, clog)); + expectStalled(98, false, 11, 0, 2, __LINE__); + EXPECT_TRUE(isStalled(followingTrue, p, false, 0, clog)); + expectStalled(97, true, 11, 0, 97, __LINE__); + EXPECT_TRUE(isStalled(followingFalse, p, false, 0, clog)); + expectStalled(96, false, 11, 0, 3, __LINE__); + + // true vote has changed recently, so not stalled + EXPECT_TRUE(!isStalled(proposingTrue, p, true, peersUnchanged, clog)); + EXPECT_TRUE(clog->str().empty()) << clog->str(); + // remaining votes have been unchanged in so long that we only + // need to hit the second round at 95% to be stalled, regardless + // of peers + EXPECT_TRUE(isStalled(proposingFalse, p, true, peersUnchanged, clog)); + expectStalled(98, false, 11, 6, 2, __LINE__); + EXPECT_TRUE(isStalled(followingTrue, p, false, peersUnchanged, clog)); + expectStalled(97, true, 11, 6, 97, __LINE__); + EXPECT_TRUE(isStalled(followingFalse, p, false, peersUnchanged, clog)); + expectStalled(96, false, 11, 6, 3, __LINE__); + } + for (int i = 1; i < 3; ++i) + { + EXPECT_TRUE(!proposingTrue.updateVote(250 + (10 * i), true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(250 + (10 * i), true, p)); + EXPECT_TRUE(!followingTrue.updateVote(250 + (10 * i), false, p)); + EXPECT_TRUE(!followingFalse.updateVote(250 + (10 * i), false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + // true vote changed 2 rounds ago, and peers are changing, so + // not stalled + EXPECT_TRUE(!isStalled(proposingTrue, p, true, 0, clog)); + EXPECT_TRUE(clog->str().empty()) << clog->str(); + // still stalled + EXPECT_TRUE(isStalled(proposingFalse, p, true, 0, clog)); + expectStalled(98, false, 11 + i, 0, 2, __LINE__); + EXPECT_TRUE(isStalled(followingTrue, p, false, 0, clog)); + expectStalled(97, true, 11 + i, 0, 97, __LINE__); + EXPECT_TRUE(isStalled(followingFalse, p, false, 0, clog)); + expectStalled(96, false, 11 + i, 0, 3, __LINE__); + + // true vote changed 2 rounds ago, and peers are NOT changing, + // so stalled + EXPECT_TRUE(isStalled(proposingTrue, p, true, peersUnchanged, clog)); + expectStalled(99, true, 1 + i, 6, 97, __LINE__); + // still stalled + EXPECT_TRUE(isStalled(proposingFalse, p, true, peersUnchanged, clog)); + expectStalled(98, false, 11 + i, 6, 2, __LINE__); + EXPECT_TRUE(isStalled(followingTrue, p, false, peersUnchanged, clog)); + expectStalled(97, true, 11 + i, 6, 97, __LINE__); + EXPECT_TRUE(isStalled(followingFalse, p, false, peersUnchanged, clog)); + expectStalled(96, false, 11 + i, 6, 3, __LINE__); + } + for (int i = 3; i < 5; ++i) + { + EXPECT_TRUE(!proposingTrue.updateVote(250 + (10 * i), true, p)); + EXPECT_TRUE(!proposingFalse.updateVote(250 + (10 * i), true, p)); + EXPECT_TRUE(!followingTrue.updateVote(250 + (10 * i), false, p)); + EXPECT_TRUE(!followingFalse.updateVote(250 + (10 * i), false, p)); + + EXPECT_TRUE(proposingTrue.getOurVote() == true); + EXPECT_TRUE(proposingFalse.getOurVote() == false); + EXPECT_TRUE(followingTrue.getOurVote() == true); + EXPECT_TRUE(followingFalse.getOurVote() == false); + + EXPECT_TRUE(isStalled(proposingTrue, p, true, 0, clog)); + expectStalled(99, true, 1 + i, 0, 97, __LINE__); + EXPECT_TRUE(isStalled(proposingFalse, p, true, 0, clog)); + expectStalled(98, false, 11 + i, 0, 2, __LINE__); + EXPECT_TRUE(isStalled(followingTrue, p, false, 0, clog)); + expectStalled(97, true, 11 + i, 0, 97, __LINE__); + EXPECT_TRUE(isStalled(followingFalse, p, false, 0, clog)); + expectStalled(96, false, 11 + i, 0, 3, __LINE__); + + EXPECT_TRUE(isStalled(proposingTrue, p, true, peersUnchanged, clog)); + expectStalled(99, true, 1 + i, 6, 97, __LINE__); + EXPECT_TRUE(isStalled(proposingFalse, p, true, peersUnchanged, clog)); + expectStalled(98, false, 11 + i, 6, 2, __LINE__); + EXPECT_TRUE(isStalled(followingTrue, p, false, peersUnchanged, clog)); + expectStalled(97, true, 11 + i, 6, 97, __LINE__); + EXPECT_TRUE(isStalled(followingFalse, p, false, peersUnchanged, clog)); + expectStalled(96, false, 11 + i, 6, 3, __LINE__); + } + } +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/consensus/DistributedValidatorsSim.cpp b/src/tests/libxrpl/consensus/DistributedValidatorsSim.cpp new file mode 100644 index 0000000000..f5ba508cbc --- /dev/null +++ b/src/tests/libxrpl/consensus/DistributedValidatorsSim.cpp @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { + +namespace { + +[[nodiscard]] std::string const& +arg() +{ + static std::string const kEMPTY; + return kEMPTY; +} + +void +completeTrustCompleteConnectFixedDelay( + std::size_t numPeers, + std::chrono::milliseconds delay = std::chrono::milliseconds(200), + bool printHeaders = false) +{ + using namespace csf; + using namespace std::chrono; + + // Initialize persistent collector logs specific to this method + std::string const prefix = + "DistributedValidators_" + "completeTrustCompleteConnectFixedDelay"; + std::fstream txLog(prefix + "_tx.csv", std::ofstream::app), + ledgerLog(prefix + "_ledger.csv", std::ofstream::app); + + // title + std::cout << prefix << "(" << numPeers << "," << delay.count() << ")" << std::endl; + + // number of peers, UNLs, connections + EXPECT_TRUE(numPeers >= 1); + + Sim sim; + PeerGroup peers = sim.createGroup(numPeers); + + // complete trust graph + peers.trust(peers); + + // complete connect graph with fixed delay + peers.connect(peers, delay); + + // Initialize collectors to track statistics to report + TxCollector txCollector; + LedgerCollector ledgerCollector; + auto colls = makeCollectors(txCollector, ledgerCollector); + sim.collectors.add(colls); + + // Initial round to set prior state + sim.run(1); + + // Run for 10 minutes, submitting 100 tx/second + std::chrono::nanoseconds const simDuration = 10min; + std::chrono::nanoseconds const quiet = 10s; + Rate const rate{.count = 100, .duration = 1000ms}; + + // Initialize timers + HeartbeatTimer heart(sim.scheduler); + + // txs, start/stop/step, target + auto peerSelector = + makeSelector(peers.begin(), peers.end(), std::vector(numPeers, 1.), sim.rng); + auto txSubmitter = makeSubmitter( + ConstantDistribution{rate.inv()}, + sim.scheduler.now() + quiet, + sim.scheduler.now() + simDuration - quiet, + peerSelector, + sim.scheduler, + sim.rng); + + // run simulation for given duration + heart.start(); + sim.run(simDuration); + + // EXPECT_TRUE(sim.branches() == 1); + // EXPECT_TRUE(sim.synchronized()); + + std::cout << std::right; + std::cout << "| Peers: " << std::setw(2) << peers.size(); + std::cout << " | Duration: " << std::setw(6) << duration_cast(simDuration).count() + << " ms"; + std::cout << " | Branches: " << std::setw(1) << sim.branches(); + std::cout << " | Synchronized: " << std::setw(1) << (sim.synchronized() ? "Y" : "N"); + std::cout << " |" << std::endl; + + txCollector.report(simDuration, std::cout, true); + ledgerCollector.report(simDuration, std::cout, false); + + std::string const tag = std::to_string(numPeers); + txCollector.csv(simDuration, txLog, tag, printHeaders); + ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders); + + std::cout << std::endl; +} + +void +completeTrustScaleFreeConnectFixedDelay( + std::size_t numPeers, + std::chrono::milliseconds delay = std::chrono::milliseconds(200), + bool printHeaders = false) +{ + using namespace csf; + using namespace std::chrono; + + // Initialize persistent collector logs specific to this method + std::string const prefix = + "DistributedValidators__" + "completeTrustScaleFreeConnectFixedDelay"; + std::fstream txLog(prefix + "_tx.csv", std::ofstream::app), + ledgerLog(prefix + "_ledger.csv", std::ofstream::app); + + // title + std::cout << prefix << "(" << numPeers << "," << delay.count() << ")" << std::endl; + + // number of peers, UNLs, connections + int const numCNLs = std::max(int(1.00 * numPeers), 1); + int const minCNLSize = std::max(int(0.25 * numCNLs), 1); + int const maxCNLSize = std::max(int(0.50 * numCNLs), 1); + EXPECT_TRUE(numPeers >= 1); + EXPECT_TRUE(numCNLs >= 1); + EXPECT_TRUE(1 <= minCNLSize && minCNLSize <= maxCNLSize && maxCNLSize <= numPeers); + + Sim sim; + PeerGroup peers = sim.createGroup(numPeers); + + // complete trust graph + peers.trust(peers); + + // scale-free connect graph with fixed delay + std::vector const ranks = sample(peers.size(), PowerLawDistribution{1, 3}, sim.rng); + randomRankedConnect( + peers, + ranks, + numCNLs, + std::uniform_int_distribution<>{minCNLSize, maxCNLSize}, + sim.rng, + delay); + + // Initialize collectors to track statistics to report + TxCollector txCollector; + LedgerCollector ledgerCollector; + auto colls = makeCollectors(txCollector, ledgerCollector); + sim.collectors.add(colls); + + // Initial round to set prior state + sim.run(1); + + // Run for 10 minutes, submitting 100 tx/second + std::chrono::nanoseconds const simDuration = 10min; + std::chrono::nanoseconds const quiet = 10s; + Rate const rate{.count = 100, .duration = 1000ms}; + + // Initialize timers + HeartbeatTimer heart(sim.scheduler); + + // txs, start/stop/step, target + auto peerSelector = + makeSelector(peers.begin(), peers.end(), std::vector(numPeers, 1.), sim.rng); + auto txSubmitter = makeSubmitter( + ConstantDistribution{rate.inv()}, + sim.scheduler.now() + quiet, + sim.scheduler.now() + simDuration - quiet, + peerSelector, + sim.scheduler, + sim.rng); + + // run simulation for given duration + heart.start(); + sim.run(simDuration); + + // EXPECT_TRUE(sim.branches() == 1); + // EXPECT_TRUE(sim.synchronized()); + + std::cout << std::right; + std::cout << "| Peers: " << std::setw(2) << peers.size(); + std::cout << " | Duration: " << std::setw(6) << duration_cast(simDuration).count() + << " ms"; + std::cout << " | Branches: " << std::setw(1) << sim.branches(); + std::cout << " | Synchronized: " << std::setw(1) << (sim.synchronized() ? "Y" : "N"); + std::cout << " |" << std::endl; + + txCollector.report(simDuration, std::cout, true); + ledgerCollector.report(simDuration, std::cout, false); + + std::string const tag = std::to_string(numPeers); + txCollector.csv(simDuration, txLog, tag, printHeaders); + ledgerCollector.csv(simDuration, ledgerLog, tag, printHeaders); + + std::cout << std::endl; +} + +} // namespace + +// In progress simulations for diversifying and distributing validators +TEST(DistributedValidatorsTest, DISABLED_distributed_validators) +{ + std::string const defaultArgs = "5 200"; + std::string const args = arg().empty() ? defaultArgs : arg(); + std::stringstream argStream(args); + + int maxNumValidators = 0; + int delayCount(200); + argStream >> maxNumValidators; + argStream >> delayCount; + + std::chrono::milliseconds const delay(delayCount); + + std::cout << "DistributedValidators: 1 to " << maxNumValidators << " Peers" << std::endl; + + // Simulate with N = 1 to N + // - complete trust graph is complete + // - complete network connectivity + // - fixed delay for network links + completeTrustCompleteConnectFixedDelay(1, delay, true); + for (int i = 2; i <= maxNumValidators; i++) + { + completeTrustCompleteConnectFixedDelay(i, delay); + } + + // Simulate with N = 1 to N + // - complete trust graph is complete + // - scale-free network connectivity + // - fixed delay for network links + completeTrustScaleFreeConnectFixedDelay(1, delay, true); + for (int i = 2; i <= maxNumValidators; i++) + { + completeTrustScaleFreeConnectFixedDelay(i, delay); + } +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/consensus/LedgerTiming.cpp b/src/tests/libxrpl/consensus/LedgerTiming.cpp new file mode 100644 index 0000000000..0cab895fda --- /dev/null +++ b/src/tests/libxrpl/consensus/LedgerTiming.cpp @@ -0,0 +1,105 @@ +#include + +#include + +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(LedgerTimingTest, get_next_ledger_time_resolution) +{ + // helper to iteratively call into getNextLedgerTimeResolution + struct TestRes + { + std::uint32_t decrease = 0; + std::uint32_t equal = 0; + std::uint32_t increase = 0; + + static TestRes + run(bool previousAgree, std::uint32_t rounds) + { + TestRes res; + auto closeResolution = kLedgerDefaultTimeResolution; + auto nextCloseResolution = closeResolution; + std::uint32_t round = 0; + do + { + nextCloseResolution = + getNextLedgerTimeResolution(closeResolution, previousAgree, ++round); + if (nextCloseResolution < closeResolution) + { + ++res.decrease; + } + else if (nextCloseResolution > closeResolution) + { + ++res.increase; + } + else + { + ++res.equal; + } + std::swap(nextCloseResolution, closeResolution); + } while (round < rounds); + return res; + } + }; + + // If we never agree on close time, only can increase resolution + // until hit the max + auto decreases = TestRes::run(false, 10); + EXPECT_TRUE(decreases.increase == 3); + EXPECT_TRUE(decreases.decrease == 0); + EXPECT_TRUE(decreases.equal == 7); + + // If we always agree on close time, only can decrease resolution + // until hit the min + auto increases = TestRes::run(false, 100); + EXPECT_TRUE(increases.increase == 3); + EXPECT_TRUE(increases.decrease == 0); + EXPECT_TRUE(increases.equal == 97); +} + +TEST(LedgerTimingTest, round_close_time) +{ + using namespace std::chrono_literals; + // A closeTime equal to the epoch is not modified + using tp = NetClock::time_point; + tp const def; + EXPECT_TRUE(def == roundCloseTime(def, 30s)); + + // Otherwise, the closeTime is rounded to the nearest + // rounding up on ties + EXPECT_TRUE(tp{0s} == roundCloseTime(tp{29s}, 60s)); + EXPECT_TRUE(tp{30s} == roundCloseTime(tp{30s}, 1s)); + EXPECT_TRUE(tp{60s} == roundCloseTime(tp{31s}, 60s)); + EXPECT_TRUE(tp{60s} == roundCloseTime(tp{30s}, 60s)); + EXPECT_TRUE(tp{60s} == roundCloseTime(tp{59s}, 60s)); + EXPECT_TRUE(tp{60s} == roundCloseTime(tp{60s}, 60s)); + EXPECT_TRUE(tp{60s} == roundCloseTime(tp{61s}, 60s)); +} + +TEST(LedgerTimingTest, eff_close_time) +{ + using namespace std::chrono_literals; + using tp = NetClock::time_point; + tp close = effCloseTime(tp{10s}, 30s, tp{0s}); + EXPECT_TRUE(close == tp{1s}); + + close = effCloseTime(tp{16s}, 30s, tp{0s}); + EXPECT_TRUE(close == tp{30s}); + + close = effCloseTime(tp{16s}, 30s, tp{30s}); + EXPECT_TRUE(close == tp{31s}); + + close = effCloseTime(tp{16s}, 30s, tp{60s}); + EXPECT_TRUE(close == tp{61s}); + + close = effCloseTime(tp{31s}, 30s, tp{0s}); + EXPECT_TRUE(close == tp{30s}); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/consensus/LedgerTrie.cpp b/src/tests/libxrpl/consensus/LedgerTrie.cpp new file mode 100644 index 0000000000..1259a5049a --- /dev/null +++ b/src/tests/libxrpl/consensus/LedgerTrie.cpp @@ -0,0 +1,693 @@ +#include + +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(LedgerTrieTest, insert) +{ + using namespace csf; + // Single entry by itself + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 1); + + t.insert(h["abc"]); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 2); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + } + // Suffix of existing (extending tree) + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + EXPECT_TRUE(t.checkInvariants()); + // extend with no siblings + t.insert(h["abcd"]); + EXPECT_TRUE(t.checkInvariants()); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 1); + + // extend with existing sibling + t.insert(h["abce"]); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 3); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abce"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abce"]) == 1); + } + // uncommitted of existing node + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + EXPECT_TRUE(t.checkInvariants()); + // uncommitted with no siblings + t.insert(h["abcdf"]); + EXPECT_TRUE(t.checkInvariants()); + + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 2); + EXPECT_TRUE(t.tipSupport(h["abcdf"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcdf"]) == 1); + + // uncommitted with existing child + t.insert(h["abc"]); + EXPECT_TRUE(t.checkInvariants()); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 3); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 2); + EXPECT_TRUE(t.tipSupport(h["abcdf"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcdf"]) == 1); + } + // Suffix + uncommitted of existing node + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + EXPECT_TRUE(t.checkInvariants()); + t.insert(h["abce"]); + EXPECT_TRUE(t.checkInvariants()); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abce"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abce"]) == 1); + } + // Suffix + uncommitted with existing child + { + // abcd : abcde, abcf + + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + EXPECT_TRUE(t.checkInvariants()); + t.insert(h["abcde"]); + EXPECT_TRUE(t.checkInvariants()); + t.insert(h["abcf"]); + EXPECT_TRUE(t.checkInvariants()); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 3); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 2); + EXPECT_TRUE(t.tipSupport(h["abcf"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcf"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abcde"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcde"]) == 1); + } + + // Multiple counts + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"], 4); + EXPECT_TRUE(t.tipSupport(h["ab"]) == 4); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 4); + EXPECT_TRUE(t.tipSupport(h["a"]) == 0); + EXPECT_TRUE(t.branchSupport(h["a"]) == 4); + + t.insert(h["abc"], 2); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 2); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + EXPECT_TRUE(t.tipSupport(h["ab"]) == 4); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 6); + EXPECT_TRUE(t.tipSupport(h["a"]) == 0); + EXPECT_TRUE(t.branchSupport(h["a"]) == 6); + } +} + +TEST(LedgerTrieTest, remove) +{ + using namespace csf; + // Not in trie + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + + EXPECT_TRUE(!t.remove(h["ab"])); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(!t.remove(h["a"])); + EXPECT_TRUE(t.checkInvariants()); + } + // In trie but with 0 tip support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"]); + t.insert(h["abce"]); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + EXPECT_TRUE(!t.remove(h["abc"])); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + } + // In trie with > 1 tip support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"], 2); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 2); + EXPECT_TRUE(t.remove(h["abc"])); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + + t.insert(h["abc"], 1); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 2); + EXPECT_TRUE(t.remove(h["abc"], 2)); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + + t.insert(h["abc"], 3); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 3); + EXPECT_TRUE(t.remove(h["abc"], 300)); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + } + // In trie with = 1 tip support, no children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + + EXPECT_TRUE(t.tipSupport(h["ab"]) == 1); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 2); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 1); + + EXPECT_TRUE(t.remove(h["abc"])); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["ab"]) == 1); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 0); + } + // In trie with = 1 tip support, 1 child + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + t.insert(h["abcd"]); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 1); + + EXPECT_TRUE(t.remove(h["abc"])); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 1); + } + // In trie with = 1 tip support, > 1 children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + t.insert(h["abcd"]); + t.insert(h["abce"]); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 3); + + EXPECT_TRUE(t.remove(h["abc"])); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 2); + } + + // In trie with = 1 tip support, parent compaction + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["abc"]); + t.insert(h["abd"]); + EXPECT_TRUE(t.checkInvariants()); + t.remove(h["ab"]); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abd"]) == 1); + EXPECT_TRUE(t.tipSupport(h["ab"]) == 0); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 2); + + t.remove(h["abd"]); + EXPECT_TRUE(t.checkInvariants()); + + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 1); + } +} + +TEST(LedgerTrieTest, empty) +{ + using namespace csf; + LedgerTrie t; + LedgerHistoryHelper h; + EXPECT_TRUE(t.empty()); + + Ledger const genesis = h[""]; + t.insert(genesis); + EXPECT_TRUE(!t.empty()); + t.remove(genesis); + EXPECT_TRUE(t.empty()); + + t.insert(h["abc"]); + EXPECT_TRUE(!t.empty()); + t.remove(h["abc"]); + EXPECT_TRUE(t.empty()); +} + +TEST(LedgerTrieTest, support) +{ + using namespace csf; + + LedgerTrie t; + LedgerHistoryHelper h; + EXPECT_TRUE(t.tipSupport(h["a"]) == 0); + EXPECT_TRUE(t.tipSupport(h["axy"]) == 0); + + EXPECT_TRUE(t.branchSupport(h["a"]) == 0); + EXPECT_TRUE(t.branchSupport(h["axy"]) == 0); + + t.insert(h["abc"]); + EXPECT_TRUE(t.tipSupport(h["a"]) == 0); + EXPECT_TRUE(t.tipSupport(h["ab"]) == 0); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abcd"]) == 0); + + EXPECT_TRUE(t.branchSupport(h["a"]) == 1); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abcd"]) == 0); + + t.insert(h["abe"]); + EXPECT_TRUE(t.tipSupport(h["a"]) == 0); + EXPECT_TRUE(t.tipSupport(h["ab"]) == 0); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 1); + EXPECT_TRUE(t.tipSupport(h["abe"]) == 1); + + EXPECT_TRUE(t.branchSupport(h["a"]) == 2); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 2); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abe"]) == 1); + + t.remove(h["abc"]); + EXPECT_TRUE(t.tipSupport(h["a"]) == 0); + EXPECT_TRUE(t.tipSupport(h["ab"]) == 0); + EXPECT_TRUE(t.tipSupport(h["abc"]) == 0); + EXPECT_TRUE(t.tipSupport(h["abe"]) == 1); + + EXPECT_TRUE(t.branchSupport(h["a"]) == 1); + EXPECT_TRUE(t.branchSupport(h["ab"]) == 1); + EXPECT_TRUE(t.branchSupport(h["abc"]) == 0); + EXPECT_TRUE(t.branchSupport(h["abe"]) == 1); +} + +TEST(LedgerTrieTest, get_preferred) +{ + using namespace csf; + using Seq = Ledger::Seq; + // Empty + { + LedgerTrie const t; + EXPECT_TRUE(t.getPreferred(Seq{0}) == std::nullopt); + EXPECT_TRUE(t.getPreferred(Seq{2}) == std::nullopt); + } + // Genesis support is NOT empty + { + LedgerTrie t; + LedgerHistoryHelper h; + Ledger const genesis = h[""]; + t.insert(genesis); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{0})->id == genesis.id()); + EXPECT_TRUE(t.remove(genesis)); + EXPECT_TRUE(t.getPreferred(Seq{0}) == std::nullopt); + EXPECT_TRUE(!t.remove(genesis)); + } + // Single node no children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abc"].id()); + } + // Single node smaller child support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"]); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abc"].id()); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abc"].id()); + } + // Single node larger child + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"], 2); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abcd"].id()); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abcd"].id()); + } + // Single node smaller children support + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"]); + t.insert(h["abce"]); + + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abc"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abc"].id()); + + t.insert(h["abc"]); + + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abc"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abc"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + } + // Single node larger children + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"], 2); + t.insert(h["abce"]); + + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abc"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abc"].id()); + + t.insert(h["abcd"]); + + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abcd"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abcd"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + } + // Tie-breaker by id + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abcd"], 2); + t.insert(h["abce"], 2); + + EXPECT_TRUE(h["abce"].id() > h["abcd"].id()); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abce"].id()); + + t.insert(h["abcd"]); + EXPECT_TRUE(h["abce"].id() > h["abcd"].id()); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abcd"].id()); + } + + // Tie-breaker not needed + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"]); + t.insert(h["abce"], 2); + // abce only has a margin of 1, but it owns the tie-breaker + EXPECT_TRUE(h["abce"].id() > h["abcd"].id()); + + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abce"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abce"].id()); + + // Switch support from abce to abcd, tie-breaker now needed + t.remove(h["abce"]); + t.insert(h["abcd"]); + + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abc"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abc"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + } + + // Single node larger grand child + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcd"], 2); + t.insert(h["abcde"], 4); + + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abcde"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abcde"].id()); + EXPECT_TRUE(t.getPreferred(Seq{5})->id == h["abcde"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + } + + // Too much uncommitted support from competing branches + { + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["abc"]); + t.insert(h["abcde"], 2); + t.insert(h["abcfg"], 2); + // 'de' and 'fg' are tied without 'abc' vote + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abc"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abc"].id()); + EXPECT_TRUE(t.getPreferred(Seq{5})->id == h["abc"].id()); + + t.remove(h["abc"]); + t.insert(h["abcd"]); + + // 'de' branch has 3 votes to 2, so earlier sequences see it as preferred + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abcde"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["abcde"].id()); + + // However, if you validated a ledger with Seq 5, potentially on + // a different branch, you do not yet know if they chose abcd + // or abcf because of you, so abc remains preferred + EXPECT_TRUE(t.getPreferred(Seq{5})->id == h["abc"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + } + + // Changing largestSeq perspective changes preferred branch + { + /** + * Build the tree below with initial tip support annotated + * A + * / \ + * B(1) C(1) + * / | | + * H D F(1) + * | + * E(2) + * | + * G + */ + LedgerTrie t; + LedgerHistoryHelper h; + t.insert(h["ab"]); + t.insert(h["ac"]); + t.insert(h["acf"]); + t.insert(h["abde"], 2); + + // B has more branch support + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{1})->id == h["ab"].id()); + EXPECT_TRUE(t.getPreferred(Seq{2})->id == h["ab"].id()); + + // But if you last validated D,F or E, you do not yet know + // if someone used that validation to commit to B or C + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["a"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["a"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + + /** + * One of E advancing to G doesn't change anything + * A + * / \ + * B(1) C(1) + * / | | + * H D F(1) + * | + * E(1) + * | + * G(1) + */ + t.remove(h["abde"]); + t.insert(h["abdeg"]); + + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{1})->id == h["ab"].id()); + EXPECT_TRUE(t.getPreferred(Seq{2})->id == h["ab"].id()); + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["a"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["a"].id()); + EXPECT_TRUE(t.getPreferred(Seq{5})->id == h["a"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + + /** + * C advancing to H does advance the seq 3 preferred ledger + * A + * / \ + * B(1) C + * / | | + * H(1)D F(1) + * | + * E(1) + * | + * G(1) + */ + t.remove(h["ac"]); + t.insert(h["abh"]); + + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{1})->id == h["ab"].id()); + EXPECT_TRUE(t.getPreferred(Seq{2})->id == h["ab"].id()); + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["ab"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["a"].id()); + EXPECT_TRUE(t.getPreferred(Seq{5})->id == h["a"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + + /** + * F advancing to E also moves the preferred ledger forward + * A + * / \ + * B(1) C + * / | | + * H(1)D F + * | + * E(2) + * | + * G(1) + */ + t.remove(h["acf"]); + t.insert(h["abde"]); + + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(t.getPreferred(Seq{1})->id == h["abde"].id()); + EXPECT_TRUE(t.getPreferred(Seq{2})->id == h["abde"].id()); + EXPECT_TRUE(t.getPreferred(Seq{3})->id == h["abde"].id()); + EXPECT_TRUE(t.getPreferred(Seq{4})->id == h["ab"].id()); + EXPECT_TRUE(t.getPreferred(Seq{5})->id == h["ab"].id()); + // NOLINTEND(bugprone-unchecked-optional-access) + } +} + +TEST(LedgerTrieTest, root_related) +{ + using namespace csf; + // Since the root is a special node that breaks the no-single child + // invariant, do some tests that exercise it. + + LedgerTrie t; + LedgerHistoryHelper h; + EXPECT_TRUE(!t.remove(h[""])); + EXPECT_TRUE(t.branchSupport(h[""]) == 0); + EXPECT_TRUE(t.tipSupport(h[""]) == 0); + + t.insert(h["a"]); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.branchSupport(h[""]) == 1); + EXPECT_TRUE(t.tipSupport(h[""]) == 0); + + t.insert(h["e"]); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.branchSupport(h[""]) == 2); + EXPECT_TRUE(t.tipSupport(h[""]) == 0); + + EXPECT_TRUE(t.remove(h["e"])); + EXPECT_TRUE(t.checkInvariants()); + EXPECT_TRUE(t.branchSupport(h[""]) == 1); + EXPECT_TRUE(t.tipSupport(h[""]) == 0); +} + +TEST(LedgerTrieTest, stress) +{ + using namespace csf; + LedgerTrie t; + LedgerHistoryHelper h; + + // Test quasi-randomly add/remove supporting for different ledgers + // from a branching history. + + // Ledgers have sequence 1,2,3,4 + std::uint32_t const depthConst = 4; + // Each ledger has 4 possible children + std::uint32_t const width = 4; + + std::uint32_t const iterations = 10000; + + // Use explicit seed to have same results for CI + // NOLINTNEXTLINE(bugprone-random-generator-seed): fixed seed for reproducible test + std::mt19937 gen{42}; + std::uniform_int_distribution<> depthDist(0, depthConst - 1); + std::uniform_int_distribution<> widthDist(0, width - 1); + std::uniform_int_distribution<> flip(0, 1); + for (std::uint32_t i = 0; i < iterations; ++i) + { + // pick a random ledger history + std::string curr; + char const depth = depthDist(gen); + char offset = 0; + for (char d = 0; d < depth; ++d) + { + char const a = offset + widthDist(gen); + curr += a; + offset = (a + 1) * width; + } + + // 50-50 to add remove + if (flip(gen) == 0) + { + t.insert(h[curr]); + } + else + { + t.remove(h[curr]); + } + EXPECT_TRUE(t.checkInvariants()); + if (!(t.checkInvariants())) + return; + } +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/consensus/ScaleFreeSim.cpp b/src/tests/libxrpl/consensus/ScaleFreeSim.cpp new file mode 100644 index 0000000000..2b07b29900 --- /dev/null +++ b/src/tests/libxrpl/consensus/ScaleFreeSim.cpp @@ -0,0 +1,100 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl::test { + +TEST(ScaleFreeSimTest, DISABLED_scale_free_sim) +{ + using namespace std::chrono; + using namespace csf; + + std::ostream& log = std::cout; + + // Generate a quasi-random scale free network and simulate consensus + // as we vary transaction submission rates + + int const n = 100; // Peers + + int const numUNLs = 15; // UNL lists + int const minUNLSize = n / 4, maxUNLSize = n / 2; + + ConsensusParms const parms{}; + Sim sim; + PeerGroup network = sim.createGroup(n); + + // generate trust ranks + std::vector const ranks = sample(network.size(), PowerLawDistribution{1, 3}, sim.rng); + + // generate scale-free trust graph + randomRankedTrust( + network, ranks, numUNLs, std::uniform_int_distribution<>{minUNLSize, maxUNLSize}, sim.rng); + + // nodes with a trust line in either direction are network-connected + network.connectFromTrust(round(0.2 * parms.ledgerGRANULARITY)); + + // Initialize collectors to track statistics to report + TxCollector txCollector; + LedgerCollector ledgerCollector; + auto colls = makeCollectors(txCollector, ledgerCollector); + sim.collectors.add(colls); + + // Initial round to set prior state + sim.run(1); + + // Initialize timers + HeartbeatTimer heart(sim.scheduler, seconds(10s)); + + // Run for 10 minutes, submitting 100 tx/second + std::chrono::nanoseconds const simDuration = 10min; + std::chrono::nanoseconds const quiet = 10s; + Rate const rate{.count = 100, .duration = 1000ms}; + + // txs, start/stop/step, target + auto peerSelector = makeSelector(network.begin(), network.end(), ranks, sim.rng); + auto txSubmitter = makeSubmitter( + ConstantDistribution{rate.inv()}, + sim.scheduler.now() + quiet, + sim.scheduler.now() + (simDuration - quiet), + peerSelector, + sim.scheduler, + sim.rng); + + // run simulation for given duration + heart.start(); + sim.run(simDuration); + + EXPECT_TRUE(sim.branches() == 1); + EXPECT_TRUE(sim.synchronized()); + + // TODO: Clean up this formatting mess!! + + log << "Peers: " << network.size() << std::endl; + log << "Simulated Duration: " << duration_cast(simDuration).count() << " ms" + << std::endl; + log << "Branches: " << sim.branches() << std::endl; + log << "Synchronized: " << (sim.synchronized() ? "Y" : "N") << std::endl; + log << std::endl; + + txCollector.report(simDuration, log); + ledgerCollector.report(simDuration, log); + // Print summary? + // # forks? # of LCLs? + // # peers + // # tx submitted + // # ledgers/sec etc.? +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/consensus/Validations.cpp b/src/tests/libxrpl/consensus/Validations.cpp new file mode 100644 index 0000000000..56964f56a6 --- /dev/null +++ b/src/tests/libxrpl/consensus/Validations.cpp @@ -0,0 +1,1030 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::csf { + +namespace { + +beast::Journal +journal() +{ + return beast::Journal{TestSink::instance()}; +} + +template +void +expireValidations(ValidationStore& validations) +{ + auto j = journal(); + validations.expire(j); +} + +using clock_type = beast::AbstractClock const; + +// Helper to convert steady_clock to a reasonable NetClock +// This allows a single manual clock in the unit tests +NetClock::time_point +toNetClock(clock_type const& c) +{ + // We don't care about the actual epochs, but do want the + // generated NetClock time to be well past its epoch to ensure + // any subtractions are positive + using namespace std::chrono; + return NetClock::time_point( + duration_cast(c.now().time_since_epoch() + 86400s)); +} + +// Represents a node that can issue validations +class Node +{ + clock_type const& c_; + PeerID nodeID_; + bool trusted_ = true; + std::size_t signIdx_{1}; + std::optional loadFee_; + +public: + Node(PeerID nodeID, clock_type const& c) : c_(c), nodeID_(nodeID) + { + } + + void + untrust() + { + trusted_ = false; + } + + void + trust() + { + trusted_ = true; + } + + void + setLoadFee(std::uint32_t fee) + { + loadFee_ = fee; + } + + [[nodiscard]] PeerID + nodeID() const + { + return nodeID_; + } + + void + advanceKey() + { + signIdx_++; + } + + [[nodiscard]] PeerKey + currKey() const + { + return std::make_pair(nodeID_, signIdx_); + } + + [[nodiscard]] PeerKey + masterKey() const + { + return std::make_pair(nodeID_, 0); + } + [[nodiscard]] NetClock::time_point + now() const + { + return toNetClock(c_); + } + + // Issue a new validation with given sequence number and id and + // with signing and seen times offset from the common clock + [[nodiscard]] Validation + validate( + Ledger::ID id, + Ledger::Seq seq, + NetClock::duration signOffset, + NetClock::duration seenOffset, + bool full) const + { + Validation v{ + id, seq, now() + signOffset, now() + seenOffset, currKey(), nodeID_, full, loadFee_}; + if (trusted_) + v.setTrusted(); + return v; + } + + [[nodiscard]] Validation + validate(Ledger ledger, NetClock::duration signOffset, NetClock::duration seenOffset) const + { + return validate(ledger.id(), ledger.seq(), signOffset, seenOffset, true); + } + + [[nodiscard]] Validation + validate(Ledger ledger) const + { + return validate( + ledger.id(), ledger.seq(), NetClock::duration{0}, NetClock::duration{0}, true); + } + + [[nodiscard]] Validation + partial(Ledger ledger) const + { + return validate( + ledger.id(), ledger.seq(), NetClock::duration{0}, NetClock::duration{0}, false); + } +}; + +// Generic Validations adaptor +class Adaptor +{ + clock_type& c_; + LedgerOracle& oracle_; + +public: + // Non-locking mutex to avoid locks in generic Validations + struct Mutex + { + void + lock() + { + } + + void + unlock() + { + } + }; + + using Validation = csf::Validation; + using Ledger = csf::Ledger; + + Adaptor(clock_type& c, LedgerOracle& o) : c_{c}, oracle_{o} + { + } + + [[nodiscard]] NetClock::time_point + now() const + { + return toNetClock(c_); + } + + std::optional + acquire(Ledger::ID const& id) + { + return oracle_.lookup(id); + } +}; + +// Specialize generic Validations using the above types +using TestValidations = Validations; + +// Gather the dependencies of TestValidations in a single class and provide +// accessors for simplifying test logic +class TestHarness +{ + ValidationParms p_; + beast::ManualClock clock_; + TestValidations tv_; + PeerID nextNodeId_{0}; + +public: + explicit TestHarness(LedgerOracle& o) : tv_(p_, clock_, clock_, o) + { + } + + ValStatus + add(Validation const& v) + { + return tv_.add(v.nodeID(), v); + } + + TestValidations& + vals() + { + return tv_; + } + + Node + makeNode() + { + return Node(nextNodeId_++, clock_); + } + + ValidationParms + parms() const + { + return p_; + } + + auto& + clock() + { + return clock_; + } +}; + +Ledger const kGenesisLedger{Ledger::MakeGenesis{}}; + +} // namespace + +TEST(ValidationsTest, add_validation) +{ + using namespace std::chrono_literals; + + SCOPED_TRACE("Add validation"); + LedgerHistoryHelper h; + Ledger const ledgerA = h["a"]; + Ledger ledgerAB = h["ab"]; + Ledger ledgerAZ = h["az"]; + Ledger ledgerABC = h["abc"]; + Ledger const ledgerABCD = h["abcd"]; + Ledger const ledgerABCDE = h["abcde"]; + + { + TestHarness harness(h.oracle); + Node n = harness.makeNode(); + + auto const v = n.validate(ledgerA); + + // Add a current validation + EXPECT_TRUE(ValStatus::Current == harness.add(v)); + + // Re-adding violates the increasing seq requirement for full + // validations + EXPECT_TRUE(ValStatus::BadSeq == harness.add(v)); + + harness.clock().advance(1s); + + EXPECT_TRUE(ValStatus::Current == harness.add(n.validate(ledgerAB))); + + // Test the node changing signing key + + // Confirm old ledger on hand, but not new ledger + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerAB.id()) == 1); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerABC.id()) == 0); + + // Rotate signing keys + n.advanceKey(); + + harness.clock().advance(1s); + + // Cannot re-do the same full validation sequence + EXPECT_TRUE(ValStatus::Conflicting == harness.add(n.validate(ledgerAB))); + // Cannot send the same partial validation sequence + EXPECT_TRUE(ValStatus::Conflicting == harness.add(n.partial(ledgerAB))); + + // Now trusts the newest ledger too + harness.clock().advance(1s); + EXPECT_TRUE(ValStatus::Current == harness.add(n.validate(ledgerABC))); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerAB.id()) == 1); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerABC.id()) == 1); + + // Processing validations out of order should ignore the older + // validation + harness.clock().advance(2s); + auto const valABCDE = n.validate(ledgerABCDE); + + harness.clock().advance(4s); + auto const valABCD = n.validate(ledgerABCD); + + EXPECT_TRUE(ValStatus::Current == harness.add(valABCD)); + + EXPECT_TRUE(ValStatus::Stale == harness.add(valABCDE)); + } + + { + // Process validations out of order with shifted times + + TestHarness harness(h.oracle); + Node const n = harness.makeNode(); + + // Establish a new current validation + EXPECT_TRUE(ValStatus::Current == harness.add(n.validate(ledgerA))); + + // Process a validation that has "later" seq but early sign time + EXPECT_TRUE(ValStatus::Stale == harness.add(n.validate(ledgerAB, -1s, -1s))); + + // Process a validation that has a later seq and later sign + // time + EXPECT_TRUE(ValStatus::Current == harness.add(n.validate(ledgerABC, 1s, 1s))); + } + + { + // Test stale on arrival validations + TestHarness harness(h.oracle); + Node const n = harness.makeNode(); + + EXPECT_TRUE( + ValStatus::Stale == + harness.add(n.validate(ledgerA, -harness.parms().validationCurrentEarly, 0s))); + + EXPECT_TRUE( + ValStatus::Stale == + harness.add(n.validate(ledgerA, harness.parms().validationCurrentWall, 0s))); + + EXPECT_TRUE( + ValStatus::Stale == + harness.add(n.validate(ledgerA, 0s, harness.parms().validationCurrentLocal))); + } + + { + // Test that full or partials cannot be sent for older sequence + // numbers, unless time-out has happened + for (bool doFull : {true, false}) + { + TestHarness harness(h.oracle); + Node n = harness.makeNode(); + + auto process = [&](Ledger& lgr) { + if (doFull) + return harness.add(n.validate(lgr)); + return harness.add(n.partial(lgr)); + }; + + EXPECT_TRUE(ValStatus::Current == process(ledgerABC)); + harness.clock().advance(1s); + EXPECT_TRUE(ledgerAB.seq() < ledgerABC.seq()); + EXPECT_TRUE(ValStatus::BadSeq == process(ledgerAB)); + + // If we advance far enough for AB to expire, we can fully + // validate or partially validate that sequence number again + EXPECT_TRUE(ValStatus::Conflicting == process(ledgerAZ)); + harness.clock().advance(harness.parms().validationSetExpires + 1ms); + EXPECT_TRUE(ValStatus::Current == process(ledgerAZ)); + } + } +} + +TEST(ValidationsTest, on_stale) +{ + SCOPED_TRACE("Stale validation"); + // Verify validation becomes stale based solely on time passing, but + // use different functions to trigger the check for staleness + + LedgerHistoryHelper h; + Ledger ledgerA = h["a"]; + Ledger const ledgerAB = h["ab"]; + + using Trigger = std::function; + + std::vector const triggers = { + [&](TestValidations& vals) { vals.currentTrusted(); }, + [&](TestValidations& vals) { vals.getCurrentNodeIDs(); }, + [&](TestValidations& vals) { vals.getPreferred(kGenesisLedger); }, + [&](TestValidations& vals) { vals.getNodesAfter(ledgerA, ledgerA.id()); }}; + for (Trigger const& trigger : triggers) + { + TestHarness harness(h.oracle); + Node const n = harness.makeNode(); + + EXPECT_TRUE(ValStatus::Current == harness.add(n.validate(ledgerAB))); + trigger(harness.vals()); + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 1); + EXPECT_TRUE( + harness.vals().getPreferred(kGenesisLedger) == + std::make_pair(ledgerAB.seq(), ledgerAB.id())); + harness.clock().advance(harness.parms().validationCurrentLocal); + + // trigger check for stale + trigger(harness.vals()); + + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 0); + EXPECT_TRUE(harness.vals().getPreferred(kGenesisLedger) == std::nullopt); + } +} + +TEST(ValidationsTest, get_nodes_after) +{ + // Test getting number of nodes working on a validation descending + // a prescribed one. This count should only be for trusted nodes, but + // includes partial and full validations + + using namespace std::chrono_literals; + SCOPED_TRACE("Get nodes after"); + + LedgerHistoryHelper h; + Ledger const ledgerA = h["a"]; + Ledger const ledgerAB = h["ab"]; + Ledger const ledgerABC = h["abc"]; + Ledger const ledgerAD = h["ad"]; + + TestHarness harness(h.oracle); + Node const trustedNode1 = harness.makeNode(); + Node const trustedNode2 = harness.makeNode(); + Node const trustedNode3 = harness.makeNode(); + + Node notTrustedNode = harness.makeNode(); + notTrustedNode.untrust(); + + // first round a,b,c agree, d has is partial + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode1.validate(ledgerA))); + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode2.validate(ledgerA))); + EXPECT_TRUE(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerA))); + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode3.partial(ledgerA))); + + for (Ledger const& ledger : {ledgerA, ledgerAB, ledgerABC, ledgerAD}) + EXPECT_TRUE(harness.vals().getNodesAfter(ledger, ledger.id()) == 0); + + harness.clock().advance(5s); + + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode1.validate(ledgerAB))); + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode2.validate(ledgerABC))); + EXPECT_TRUE(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerAB))); + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode3.partial(ledgerABC))); + + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerA, ledgerA.id()) == 3); + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerAB, ledgerAB.id()) == 2); + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerABC, ledgerABC.id()) == 0); + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerAD, ledgerAD.id()) == 0); + + // If given a ledger inconsistent with the id, is still able to check using slower method + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerAD, ledgerA.id()) == 1); + EXPECT_TRUE(harness.vals().getNodesAfter(ledgerAD, ledgerAB.id()) == 2); +} + +TEST(ValidationsTest, current_trusted) +{ + using namespace std::chrono_literals; + SCOPED_TRACE("Current trusted validations"); + + LedgerHistoryHelper h; + Ledger const ledgerA = h["a"]; + Ledger const ledgerB = h["b"]; + Ledger const ledgerAC = h["ac"]; + + TestHarness harness(h.oracle); + Node const a = harness.makeNode(); + Node b = harness.makeNode(); + b.untrust(); + + EXPECT_TRUE(ValStatus::Current == harness.add(a.validate(ledgerA))); + EXPECT_TRUE(ValStatus::Current == harness.add(b.validate(ledgerB))); + + // Only a is trusted + EXPECT_TRUE(harness.vals().currentTrusted().size() == 1); + EXPECT_TRUE(harness.vals().currentTrusted()[0].ledgerID() == ledgerA.id()); + EXPECT_TRUE(harness.vals().currentTrusted()[0].seq() == ledgerA.seq()); + + harness.clock().advance(3s); + + for (auto const& node : {a, b}) + EXPECT_TRUE(ValStatus::Current == harness.add(node.validate(ledgerAC))); + + // New validation for a + EXPECT_TRUE(harness.vals().currentTrusted().size() == 1); + EXPECT_TRUE(harness.vals().currentTrusted()[0].ledgerID() == ledgerAC.id()); + EXPECT_TRUE(harness.vals().currentTrusted()[0].seq() == ledgerAC.seq()); + + // Pass enough time for it to go stale + harness.clock().advance(harness.parms().validationCurrentLocal); + EXPECT_TRUE(harness.vals().currentTrusted().empty()); +} + +TEST(ValidationsTest, get_current_public_keys) +{ + using namespace std::chrono_literals; + SCOPED_TRACE("Current public keys"); + + LedgerHistoryHelper h; + Ledger const ledgerA = h["a"]; + Ledger const ledgerAC = h["ac"]; + + TestHarness harness(h.oracle); + Node a = harness.makeNode(), b = harness.makeNode(); + b.untrust(); + + for (auto const& node : {a, b}) + EXPECT_TRUE(ValStatus::Current == harness.add(node.validate(ledgerA))); + + { + hash_set const expectedKeys = {a.nodeID(), b.nodeID()}; + EXPECT_TRUE(harness.vals().getCurrentNodeIDs() == expectedKeys); + } + + harness.clock().advance(3s); + + // Change keys and issue partials + a.advanceKey(); + b.advanceKey(); + + for (auto const& node : {a, b}) + EXPECT_TRUE(ValStatus::Current == harness.add(node.partial(ledgerAC))); + + { + hash_set const expectedKeys = {a.nodeID(), b.nodeID()}; + EXPECT_TRUE(harness.vals().getCurrentNodeIDs() == expectedKeys); + } + + // Pass enough time for them to go stale + harness.clock().advance(harness.parms().validationCurrentLocal); + EXPECT_TRUE(harness.vals().getCurrentNodeIDs().empty()); +} + +TEST(ValidationsTest, trusted_by_ledger_functions) +{ + // Test the Validations functions that calculate a value by ledger ID + using namespace std::chrono_literals; + SCOPED_TRACE("By ledger functions"); + + // Several Validations functions return a set of values associated + // with trusted ledgers sharing the same ledger ID. The tests below + // exercise this logic by saving the set of trusted Validations, and + // verifying that the Validations member functions all calculate the + // proper transformation of the available ledgers. + + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + + Node a = harness.makeNode(), b = harness.makeNode(), c = harness.makeNode(), + d = harness.makeNode(), e = harness.makeNode(); + + c.untrust(); + // Mix of load fees + a.setLoadFee(12); + b.setLoadFee(1); + c.setLoadFee(12); + e.setLoadFee(12); + + hash_map, std::vector> trustedValidations; + + //---------------------------------------------------------------------- + // checkers + auto sorted = [](auto vec) { + std::sort(vec.begin(), vec.end()); + return vec; + }; + auto compare = [&]() { + for (auto& it : trustedValidations) + { + auto const& id = it.first.first; + auto const& seq = it.first.second; + auto const& expectedValidations = it.second; + + EXPECT_TRUE(harness.vals().numTrustedForLedger(id) == expectedValidations.size()); + EXPECT_TRUE( + sorted(harness.vals().getTrustedForLedger(id, seq)) == sorted(expectedValidations)); + + std::uint32_t const baseFee = 0; + std::vector expectedFees; + expectedFees.reserve(expectedValidations.size()); + for (auto const& val : expectedValidations) + { + expectedFees.push_back(val.loadFee().value_or(baseFee)); + } + + EXPECT_TRUE(sorted(harness.vals().fees(id, baseFee)) == sorted(expectedFees)); + } + }; + + //---------------------------------------------------------------------- + Ledger const ledgerA = h["a"]; + Ledger const ledgerB = h["b"]; + Ledger const ledgerAC = h["ac"]; + + // Add a dummy ID to cover unknown ledger identifiers + trustedValidations[{Ledger::ID{100}, Ledger::Seq{100}}] = {}; + + // first round a,b,c agree + for (auto const& node : {a, b, c}) + { + auto const val = node.validate(ledgerA); + EXPECT_TRUE(ValStatus::Current == harness.add(val)); + if (val.trusted()) + trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); + } + // d disagrees + { + auto const val = d.validate(ledgerB); + EXPECT_TRUE(ValStatus::Current == harness.add(val)); + trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); + } + // e only issues partials + { + EXPECT_TRUE(ValStatus::Current == harness.add(e.partial(ledgerA))); + } + + harness.clock().advance(5s); + // second round, a,b,c move to ledger 2 + for (auto const& node : {a, b, c}) + { + auto const val = node.validate(ledgerAC); + EXPECT_TRUE(ValStatus::Current == harness.add(val)); + if (val.trusted()) + trustedValidations[{val.ledgerID(), val.seq()}].emplace_back(val); + } + // d now thinks ledger 1, but cannot re-issue a previously used seq + // and attempting it should generate a conflict. + { + EXPECT_TRUE(ValStatus::Conflicting == harness.add(d.partial(ledgerA))); + } + // e only issues partials + { + EXPECT_TRUE(ValStatus::Current == harness.add(e.partial(ledgerAC))); + } + + compare(); +} + +TEST(ValidationsTest, expire) +{ + // Verify expiring clears out validations stored by ledger + SCOPED_TRACE("Expire validations"); + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const a = harness.makeNode(); + constexpr Ledger::Seq kOne(1); + constexpr Ledger::Seq kTwo(2); + + // simple cases + Ledger const ledgerA = h["a"]; + EXPECT_TRUE(ValStatus::Current == harness.add(a.validate(ledgerA))); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); + expireValidations(harness.vals()); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); + harness.clock().advance(harness.parms().validationSetExpires); + expireValidations(harness.vals()); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); + + // use setSeqToKeep to keep the validation from expire + Ledger const ledgerB = h["ab"]; + EXPECT_TRUE(ValStatus::Current == harness.add(a.validate(ledgerB))); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); + harness.vals().setSeqToKeep(ledgerB.seq(), ledgerB.seq() + kOne); + harness.clock().advance(harness.parms().validationSetExpires); + expireValidations(harness.vals()); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); + // change toKeep + harness.vals().setSeqToKeep(ledgerB.seq() + kOne, ledgerB.seq() + kTwo); + // advance clock slowly + int const loops = + harness.parms().validationSetExpires / harness.parms().validationFRESHNESS + 1; + for (int i = 0; i < loops; ++i) + { + harness.clock().advance(harness.parms().validationFRESHNESS); + expireValidations(harness.vals()); + } + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerB.id()) == 0); + + // Allow the validation with high seq to expire + Ledger const ledgerC = h["abc"]; + EXPECT_TRUE(ValStatus::Current == harness.add(a.validate(ledgerC))); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerC.id()) == 1); + harness.vals().setSeqToKeep(ledgerC.seq() - kOne, ledgerC.seq()); + harness.clock().advance(harness.parms().validationSetExpires); + expireValidations(harness.vals()); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerC.id()) == 0); +} + +TEST(ValidationsTest, flush) +{ + // Test final flush of validations + using namespace std::chrono_literals; + SCOPED_TRACE("Flush validations"); + + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const trustedNode1 = harness.makeNode(); + Node const trustedNode2 = harness.makeNode(); + Node notTrustedNode = harness.makeNode(); + notTrustedNode.untrust(); + + Ledger const ledgerA = h["a"]; + Ledger const ledgerAB = h["ab"]; + + hash_map expected; + for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode}) + { + auto const val = node.validate(ledgerA); + EXPECT_TRUE(ValStatus::Current == harness.add(val)); + expected.emplace(node.nodeID(), val); + } + + // Send in a new validation for a, saving the new one into the expected + // map after setting the proper prior ledger ID it replaced + harness.clock().advance(1s); + auto newVal = trustedNode1.validate(ledgerAB); + EXPECT_TRUE(ValStatus::Current == harness.add(newVal)); + expected.find(trustedNode1.nodeID())->second = newVal; +} + +TEST(ValidationsTest, get_preferred_ledger) +{ + using namespace std::chrono_literals; + SCOPED_TRACE("Preferred Ledger"); + + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const trustedNode1 = harness.makeNode(); + Node const trustedNode2 = harness.makeNode(); + Node const trustedNode3 = harness.makeNode(); + + Node notTrustedNode = harness.makeNode(); + notTrustedNode.untrust(); + + Ledger const ledgerA = h["a"]; + Ledger const ledgerB = h["b"]; + Ledger const ledgerAC = h["ac"]; + Ledger const ledgerACD = h["acd"]; + + using Seq = Ledger::Seq; + + auto pref = [](Ledger ledger) { return std::make_pair(ledger.seq(), ledger.id()); }; + + // Empty (no ledgers) + EXPECT_TRUE(harness.vals().getPreferred(ledgerA) == std::nullopt); + + // Single ledger + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode1.validate(ledgerB))); + EXPECT_TRUE(harness.vals().getPreferred(ledgerA) == pref(ledgerB)); + EXPECT_TRUE(harness.vals().getPreferred(ledgerB) == pref(ledgerB)); + + // Minimum valid sequence + EXPECT_TRUE(harness.vals().getPreferred(ledgerA, Seq{10}) == ledgerA.id()); + + // Untrusted doesn't impact preferred ledger + // (ledgerB has tie-break over ledgerA) + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode2.validate(ledgerA))); + EXPECT_TRUE(ValStatus::Current == harness.add(notTrustedNode.validate(ledgerA))); + EXPECT_TRUE(ledgerB.id() > ledgerA.id()); + EXPECT_TRUE(harness.vals().getPreferred(ledgerA) == pref(ledgerB)); + EXPECT_TRUE(harness.vals().getPreferred(ledgerB) == pref(ledgerB)); + + // Partial does break ties + EXPECT_TRUE(ValStatus::Current == harness.add(trustedNode3.partial(ledgerA))); + EXPECT_TRUE(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); + EXPECT_TRUE(harness.vals().getPreferred(ledgerB) == pref(ledgerA)); + + harness.clock().advance(5s); + + // Parent of preferred-> stick with ledger + for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode, trustedNode3}) + EXPECT_TRUE(ValStatus::Current == harness.add(node.validate(ledgerAC))); + // Parent of preferred stays put + EXPECT_TRUE(harness.vals().getPreferred(ledgerA) == pref(ledgerA)); + // Earlier different chain, switch + EXPECT_TRUE(harness.vals().getPreferred(ledgerB) == pref(ledgerAC)); + // Later on chain, stays where it is + EXPECT_TRUE(harness.vals().getPreferred(ledgerACD) == pref(ledgerACD)); + + // Any later grandchild or different chain is preferred + harness.clock().advance(5s); + for (auto const& node : {trustedNode1, trustedNode2, notTrustedNode, trustedNode3}) + EXPECT_TRUE(ValStatus::Current == harness.add(node.validate(ledgerACD))); + for (auto const& ledger : {ledgerA, ledgerB, ledgerACD}) + EXPECT_TRUE(harness.vals().getPreferred(ledger) == pref(ledgerACD)); +} + +TEST(ValidationsTest, get_preferred_lcl) +{ + using namespace std::chrono_literals; + SCOPED_TRACE("Get preferred LCL"); + + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const a = harness.makeNode(); + + Ledger const ledgerA = h["a"]; + Ledger const ledgerB = h["b"]; + Ledger const ledgerC = h["c"]; + + using ID = Ledger::ID; + using Seq = Ledger::Seq; + + hash_map peerCounts; + + // No trusted validations or counts sticks with current ledger + EXPECT_TRUE(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerA.id()); + + ++peerCounts[ledgerB.id()]; + + // No trusted validations, rely on peer counts + EXPECT_TRUE(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerB.id()); + + ++peerCounts[ledgerC.id()]; + // No trusted validations, tied peers goes with larger ID + EXPECT_TRUE(ledgerC.id() > ledgerB.id()); + + EXPECT_TRUE(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerC.id()); + + peerCounts[ledgerC.id()] += 1000; + + // Single trusted always wins over peer counts + EXPECT_TRUE(ValStatus::Current == harness.add(a.validate(ledgerA))); + EXPECT_TRUE(harness.vals().getPreferredLCL(ledgerA, Seq{0}, peerCounts) == ledgerA.id()); + EXPECT_TRUE(harness.vals().getPreferredLCL(ledgerB, Seq{0}, peerCounts) == ledgerA.id()); + EXPECT_TRUE(harness.vals().getPreferredLCL(ledgerC, Seq{0}, peerCounts) == ledgerA.id()); + + // Stick with current ledger if trusted validation ledger has too old + // of a sequence + EXPECT_TRUE(harness.vals().getPreferredLCL(ledgerB, Seq{2}, peerCounts) == ledgerB.id()); +} + +TEST(ValidationsTest, acquire_validated_ledger) +{ + using namespace std::chrono_literals; + SCOPED_TRACE("Acquire validated ledger"); + + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const a = harness.makeNode(); + Node const b = harness.makeNode(); + + using ID = Ledger::ID; + using Seq = Ledger::Seq; + + // Validate the ledger before it is actually available + Validation const val = a.validate(ID{2}, Seq{2}, 0s, 0s, true); + + EXPECT_TRUE(ValStatus::Current == harness.add(val)); + // Validation is available + EXPECT_TRUE(harness.vals().numTrustedForLedger(ID{2}) == 1); + // but ledger based data is not + EXPECT_TRUE(harness.vals().getNodesAfter(kGenesisLedger, ID{0}) == 0); + // Initial preferred branch falls back to the ledger we are trying to + // acquire + EXPECT_TRUE(harness.vals().getPreferred(kGenesisLedger) == std::make_pair(Seq{2}, ID{2})); + + // After adding another unavailable validation, the preferred ledger + // breaks ties via higher ID + EXPECT_TRUE(ValStatus::Current == harness.add(b.validate(ID{3}, Seq{2}, 0s, 0s, true))); + EXPECT_TRUE(harness.vals().getPreferred(kGenesisLedger) == std::make_pair(Seq{2}, ID{3})); + + // Create the ledger + Ledger const ledgerAB = h["ab"]; + // Now it should be available + EXPECT_TRUE(harness.vals().getNodesAfter(kGenesisLedger, ID{0}) == 1); + + // Create a validation that is not available + harness.clock().advance(5s); + Validation const val2 = a.validate(ID{4}, Seq{4}, 0s, 0s, true); + EXPECT_TRUE(ValStatus::Current == harness.add(val2)); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ID{4}) == 1); + EXPECT_TRUE( + harness.vals().getPreferred(kGenesisLedger) == + std::make_pair(ledgerAB.seq(), ledgerAB.id())); + + // Another node requesting that ledger still doesn't change things + Validation const val3 = b.validate(ID{4}, Seq{4}, 0s, 0s, true); + EXPECT_TRUE(ValStatus::Current == harness.add(val3)); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ID{4}) == 2); + EXPECT_TRUE( + harness.vals().getPreferred(kGenesisLedger) == + std::make_pair(ledgerAB.seq(), ledgerAB.id())); + + // Switch to validation that is available + harness.clock().advance(5s); + Ledger const ledgerABCDE = h["abcde"]; + EXPECT_TRUE(ValStatus::Current == harness.add(a.partial(ledgerABCDE))); + EXPECT_TRUE(ValStatus::Current == harness.add(b.partial(ledgerABCDE))); + EXPECT_TRUE( + harness.vals().getPreferred(kGenesisLedger) == + std::make_pair(ledgerABCDE.seq(), ledgerABCDE.id())); +} + +TEST(ValidationsTest, num_trusted_for_ledger) +{ + SCOPED_TRACE("NumTrustedForLedger"); + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const a = harness.makeNode(); + Node const b = harness.makeNode(); + Ledger const ledgerA = h["a"]; + + EXPECT_TRUE(ValStatus::Current == harness.add(a.partial(ledgerA))); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); + + EXPECT_TRUE(ValStatus::Current == harness.add(b.validate(ledgerA))); + EXPECT_TRUE(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); +} + +TEST(ValidationsTest, seq_enforcer) +{ + SCOPED_TRACE("SeqEnforcer"); + using Seq = Ledger::Seq; + using namespace std::chrono; + + beast::ManualClock clock; + SeqEnforcer enforcer; + + ValidationParms const p; + + EXPECT_TRUE(enforcer(clock.now(), Seq{1}, p)); + EXPECT_TRUE(enforcer(clock.now(), Seq{10}, p)); + EXPECT_TRUE(!enforcer(clock.now(), Seq{5}, p)); + EXPECT_TRUE(!enforcer(clock.now(), Seq{9}, p)); + clock.advance(p.validationSetExpires - 1ms); + EXPECT_TRUE(!enforcer(clock.now(), Seq{1}, p)); + clock.advance(2ms); + EXPECT_TRUE(enforcer(clock.now(), Seq{1}, p)); +} + +TEST(ValidationsTest, trust_changed) +{ + SCOPED_TRACE("TrustChanged"); + using namespace std::chrono; + + auto checker = [&](TestValidations& vals, + hash_set const& listed, + std::vector const& trustedVals) { + Ledger::ID const testID = + trustedVals.empty() ? kGenesisLedger.id() : trustedVals[0].ledgerID(); + Ledger::Seq const testSeq = + trustedVals.empty() ? kGenesisLedger.seq() : trustedVals[0].seq(); + EXPECT_TRUE(vals.currentTrusted() == trustedVals); + EXPECT_TRUE(vals.getCurrentNodeIDs() == listed); + EXPECT_TRUE(vals.getNodesAfter(kGenesisLedger, kGenesisLedger.id()) == trustedVals.size()); + if (trustedVals.empty()) + { + EXPECT_TRUE(vals.getPreferred(kGenesisLedger) == std::nullopt); + } + else + { + EXPECT_TRUE(vals.getPreferred(kGenesisLedger)->second == testID); + } + EXPECT_TRUE(vals.getTrustedForLedger(testID, testSeq) == trustedVals); + EXPECT_TRUE(vals.numTrustedForLedger(testID) == trustedVals.size()); + }; + + { + // Trusted to untrusted + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const a = harness.makeNode(); + Ledger const ledgerAB = h["ab"]; + Validation const v = a.validate(ledgerAB); + EXPECT_TRUE(ValStatus::Current == harness.add(v)); + + hash_set const listed({a.nodeID()}); + std::vector trustedVals({v}); + checker(harness.vals(), listed, trustedVals); + + trustedVals.clear(); + harness.vals().trustChanged({}, {a.nodeID()}); + checker(harness.vals(), listed, trustedVals); + } + + { + // Untrusted to trusted + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node a = harness.makeNode(); + a.untrust(); + Ledger const ledgerAB = h["ab"]; + Validation const v = a.validate(ledgerAB); + EXPECT_TRUE(ValStatus::Current == harness.add(v)); + + hash_set const listed({a.nodeID()}); + std::vector trustedVals; + checker(harness.vals(), listed, trustedVals); + + trustedVals.push_back(v); + harness.vals().trustChanged({a.nodeID()}, {}); + checker(harness.vals(), listed, trustedVals); + } + + { + // Trusted but not acquired -> untrusted + LedgerHistoryHelper h; + TestHarness harness(h.oracle); + Node const a = harness.makeNode(); + Validation const v = a.validate(Ledger::ID{2}, Ledger::Seq{2}, 0s, 0s, true); + EXPECT_TRUE(ValStatus::Current == harness.add(v)); + + hash_set const listed({a.nodeID()}); + std::vector trustedVals({v}); + auto& vals = harness.vals(); + EXPECT_TRUE(vals.currentTrusted() == trustedVals); + + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_TRUE(vals.getPreferred(kGenesisLedger)->second == v.ledgerID()); + EXPECT_TRUE(vals.getNodesAfter(kGenesisLedger, kGenesisLedger.id()) == 0); + + trustedVals.clear(); + harness.vals().trustChanged({}, {a.nodeID()}); + // make acquiring ledger available + h["ab"]; + EXPECT_TRUE(vals.currentTrusted() == trustedVals); + EXPECT_TRUE(vals.getPreferred(kGenesisLedger) == std::nullopt); + EXPECT_TRUE(vals.getNodesAfter(kGenesisLedger, kGenesisLedger.id()) == 0); + } +} + +} // namespace xrpl::test::csf diff --git a/src/tests/libxrpl/csf/BasicNetwork.cpp b/src/tests/libxrpl/csf/BasicNetwork.cpp new file mode 100644 index 0000000000..a6fc2d90e6 --- /dev/null +++ b/src/tests/libxrpl/csf/BasicNetwork.cpp @@ -0,0 +1,122 @@ +#include + +#include +#include + +#include +#include + +namespace xrpl::test { + +namespace { + +struct Peer +{ + int id; + std::set set; + + Peer(Peer const&) = default; + Peer(Peer&&) = default; + + explicit Peer(int id) : id(id) + { + } + + template + void + start(csf::Scheduler& scheduler, Net& net) + { + using namespace std::chrono_literals; + auto t = scheduler.in(1s, [&] { set.insert(0); }); + if (id == 0) + { + for (auto const link : net.links(this)) + { + net.send(this, link.target, [&, to = link.target] { to->receive(net, this, 1); }); + } + } + else + { + scheduler.cancel(t); + } + } + + template + void + receive(Net& net, Peer* from, int m) + { + set.insert(m); + ++m; + if (m < 5) + { + for (auto const link : net.links(this)) + { + net.send(this, link.target, [&, mm = m, to = link.target] { + to->receive(net, this, mm); + }); + } + } + } +}; + +} // namespace + +TEST(BasicNetworkTest, network) +{ + using namespace std::chrono_literals; + std::vector pv; + pv.emplace_back(0); + pv.emplace_back(1); + pv.emplace_back(2); + csf::Scheduler scheduler; + csf::BasicNetwork net(scheduler); + EXPECT_TRUE(!net.connect(&pv[0], &pv[0])); + EXPECT_TRUE(net.connect(&pv[0], &pv[1], 1s)); + EXPECT_TRUE(net.connect(&pv[1], &pv[2], 1s)); + EXPECT_TRUE(!net.connect(&pv[0], &pv[1])); + for (auto& peer : pv) + peer.start(scheduler, net); + EXPECT_TRUE(scheduler.stepFor(0s)); + EXPECT_TRUE(scheduler.stepFor(1s)); + EXPECT_TRUE(scheduler.step()); + EXPECT_TRUE(!scheduler.step()); + EXPECT_TRUE(!scheduler.stepFor(1s)); + net.send(&pv[0], &pv[1], [] {}); + net.send(&pv[1], &pv[0], [] {}); + EXPECT_TRUE(net.disconnect(&pv[0], &pv[1])); + EXPECT_TRUE(!net.disconnect(&pv[0], &pv[1])); + for (;;) + { + auto const links = net.links(&pv[1]); + if (links.empty()) + break; + EXPECT_TRUE(net.disconnect(&pv[1], links[0].target)); + } + EXPECT_TRUE(pv[0].set == std::set({0, 2, 4})); + EXPECT_TRUE(pv[1].set == std::set({1, 3})); + EXPECT_TRUE(pv[2].set == std::set({2, 4})); +} + +TEST(BasicNetworkTest, disconnect) +{ + using namespace std::chrono_literals; + csf::Scheduler scheduler; + csf::BasicNetwork net(scheduler); + EXPECT_TRUE(net.connect(0, 1, 1s)); + EXPECT_TRUE(net.connect(0, 2, 2s)); + + std::set delivered; + net.send(0, 1, [&]() { delivered.insert(1); }); + net.send(0, 2, [&]() { delivered.insert(2); }); + + scheduler.in(1000ms, [&]() { EXPECT_TRUE(net.disconnect(0, 2)); }); + scheduler.in(1100ms, [&]() { EXPECT_TRUE(net.connect(0, 2)); }); + + scheduler.step(); + + // only the first message is delivered because the disconnect at 1 s + // purges all pending messages from 0 to 2 + EXPECT_TRUE(delivered == std::set({1})); +} + +} // namespace xrpl::test diff --git a/src/test/csf/BasicNetwork.h b/src/tests/libxrpl/csf/BasicNetwork.h similarity index 99% rename from src/test/csf/BasicNetwork.h rename to src/tests/libxrpl/csf/BasicNetwork.h index 0428475504..2c68bd282d 100644 --- a/src/test/csf/BasicNetwork.h +++ b/src/tests/libxrpl/csf/BasicNetwork.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include diff --git a/src/test/csf/CollectorRef.h b/src/tests/libxrpl/csf/CollectorRef.h similarity index 97% rename from src/test/csf/CollectorRef.h rename to src/tests/libxrpl/csf/CollectorRef.h index 3aef4d617f..b1da962f3d 100644 --- a/src/test/csf/CollectorRef.h +++ b/src/tests/libxrpl/csf/CollectorRef.h @@ -1,11 +1,11 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/tests/libxrpl/csf/Digraph.cpp b/src/tests/libxrpl/csf/Digraph.cpp new file mode 100644 index 0000000000..83c15ec06a --- /dev/null +++ b/src/tests/libxrpl/csf/Digraph.cpp @@ -0,0 +1,72 @@ +#include + +#include + +#include +#include +#include +#include + +namespace xrpl::test { + +TEST(DigraphTest, digraph) +{ + using namespace csf; + using Graph = Digraph; + Graph graph; + + EXPECT_TRUE(!graph.connected('a', 'b')); + EXPECT_TRUE(!graph.edge('a', 'b')); + EXPECT_TRUE(!graph.disconnect('a', 'b')); + + EXPECT_TRUE(graph.connect('a', 'b', "foobar")); + EXPECT_TRUE(graph.connected('a', 'b')); + EXPECT_TRUE(*graph.edge('a', 'b') == "foobar"); // NOLINT(bugprone-unchecked-optional-access) + + EXPECT_TRUE(!graph.connect('a', 'b', "repeat")); + EXPECT_TRUE(graph.disconnect('a', 'b')); + EXPECT_TRUE(graph.connect('a', 'b', "repeat")); + EXPECT_TRUE(graph.connected('a', 'b')); + EXPECT_TRUE(*graph.edge('a', 'b') == "repeat"); // NOLINT(bugprone-unchecked-optional-access) + + EXPECT_TRUE(graph.connect('a', 'c', "tree")); + + { + std::vector> edges; + + for (auto const& edge : graph.outEdges('a')) + { + edges.emplace_back(edge.source, edge.target, edge.data); + } + + std::vector> expected; + expected.emplace_back('a', 'b', "repeat"); + expected.emplace_back('a', 'c', "tree"); + EXPECT_TRUE(edges == expected); + EXPECT_TRUE(graph.outDegree('a') == expected.size()); + } + + EXPECT_TRUE(graph.outEdges('r').size() == 0); + EXPECT_TRUE(graph.outDegree('r') == 0); + EXPECT_TRUE(graph.outDegree('c') == 0); + + // only 'a' has out edges + EXPECT_TRUE(graph.outVertices().size() == 1); + std::vector const expected = {'b', 'c'}; + + EXPECT_TRUE((graph.outVertices('a') == expected)); + EXPECT_TRUE(graph.outVertices('b').size() == 0); + EXPECT_TRUE(graph.outVertices('c').size() == 0); + EXPECT_TRUE(graph.outVertices('r').size() == 0); + + std::stringstream ss; + graph.saveDot(ss, [](char v) { return v; }); + std::string const expectedDot = + "digraph {\n" + "a -> b;\n" + "a -> c;\n" + "}\n"; + EXPECT_TRUE(ss.str() == expectedDot); +} + +} // namespace xrpl::test diff --git a/src/test/csf/Digraph.h b/src/tests/libxrpl/csf/Digraph.h similarity index 100% rename from src/test/csf/Digraph.h rename to src/tests/libxrpl/csf/Digraph.h diff --git a/src/tests/libxrpl/csf/Histogram.cpp b/src/tests/libxrpl/csf/Histogram.cpp new file mode 100644 index 0000000000..6de1cde593 --- /dev/null +++ b/src/tests/libxrpl/csf/Histogram.cpp @@ -0,0 +1,59 @@ +#include + +#include + +namespace xrpl::test { + +TEST(HistogramTest, histogram) +{ + using namespace csf; + Histogram hist; + + EXPECT_TRUE(hist.size() == 0); + EXPECT_TRUE(hist.numBins() == 0); + EXPECT_TRUE(hist.minValue() == 0); + EXPECT_TRUE(hist.maxValue() == 0); + EXPECT_TRUE(hist.avg() == 0); + EXPECT_TRUE(hist.percentile(0.0f) == hist.minValue()); + EXPECT_TRUE(hist.percentile(0.5f) == 0); + EXPECT_TRUE(hist.percentile(0.9f) == 0); + EXPECT_TRUE(hist.percentile(1.0f) == hist.maxValue()); + + hist.insert(1); + + EXPECT_TRUE(hist.size() == 1); + EXPECT_TRUE(hist.numBins() == 1); + EXPECT_TRUE(hist.minValue() == 1); + EXPECT_TRUE(hist.maxValue() == 1); + EXPECT_TRUE(hist.avg() == 1); + EXPECT_TRUE(hist.percentile(0.0f) == hist.minValue()); + EXPECT_TRUE(hist.percentile(0.5f) == 1); + EXPECT_TRUE(hist.percentile(0.9f) == 1); + EXPECT_TRUE(hist.percentile(1.0f) == hist.maxValue()); + + hist.insert(9); + + EXPECT_TRUE(hist.size() == 2); + EXPECT_TRUE(hist.numBins() == 2); + EXPECT_TRUE(hist.minValue() == 1); + EXPECT_TRUE(hist.maxValue() == 9); + EXPECT_TRUE(hist.avg() == 5); + EXPECT_TRUE(hist.percentile(0.0f) == hist.minValue()); + EXPECT_TRUE(hist.percentile(0.5f) == 1); + EXPECT_TRUE(hist.percentile(0.9f) == 9); + EXPECT_TRUE(hist.percentile(1.0f) == hist.maxValue()); + + hist.insert(1); + + EXPECT_TRUE(hist.size() == 3); + EXPECT_TRUE(hist.numBins() == 2); + EXPECT_TRUE(hist.minValue() == 1); + EXPECT_TRUE(hist.maxValue() == 9); + EXPECT_TRUE(hist.avg() == 11 / 3); + EXPECT_TRUE(hist.percentile(0.0f) == hist.minValue()); + EXPECT_TRUE(hist.percentile(0.5f) == 1); + EXPECT_TRUE(hist.percentile(0.9f) == 9); + EXPECT_TRUE(hist.percentile(1.0f) == hist.maxValue()); +} + +} // namespace xrpl::test diff --git a/src/test/csf/Histogram.h b/src/tests/libxrpl/csf/Histogram.h similarity index 100% rename from src/test/csf/Histogram.h rename to src/tests/libxrpl/csf/Histogram.h diff --git a/src/test/csf/Peer.h b/src/tests/libxrpl/csf/Peer.h similarity index 98% rename from src/test/csf/Peer.h rename to src/tests/libxrpl/csf/Peer.h index 79bffec9cb..d4b6f42bdb 100644 --- a/src/test/csf/Peer.h +++ b/src/tests/libxrpl/csf/Peer.h @@ -1,33 +1,32 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - #include #include #include #include #include #include +#include +#include +#include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include diff --git a/src/test/csf/PeerGroup.h b/src/tests/libxrpl/csf/PeerGroup.h similarity index 98% rename from src/test/csf/PeerGroup.h rename to src/tests/libxrpl/csf/PeerGroup.h index 1c31209ef3..ff99b779a3 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/tests/libxrpl/csf/PeerGroup.h @@ -1,9 +1,9 @@ #pragma once -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/test/csf/Proposal.h b/src/tests/libxrpl/csf/Proposal.h similarity index 66% rename from src/test/csf/Proposal.h rename to src/tests/libxrpl/csf/Proposal.h index ecf430ae8d..b2a97f9731 100644 --- a/src/test/csf/Proposal.h +++ b/src/tests/libxrpl/csf/Proposal.h @@ -1,10 +1,10 @@ #pragma once -#include -#include -#include +#include -#include +#include +#include +#include namespace xrpl::test::csf { /** diff --git a/src/test/csf/README.md b/src/tests/libxrpl/csf/README.md similarity index 100% rename from src/test/csf/README.md rename to src/tests/libxrpl/csf/README.md diff --git a/src/tests/libxrpl/csf/Scheduler.cpp b/src/tests/libxrpl/csf/Scheduler.cpp new file mode 100644 index 0000000000..63871e0623 --- /dev/null +++ b/src/tests/libxrpl/csf/Scheduler.cpp @@ -0,0 +1,61 @@ +#include + +#include + +#include + +namespace xrpl::test { + +TEST(SchedulerTest, scheduler) +{ + using namespace std::chrono_literals; + csf::Scheduler scheduler; + std::set seen; + + scheduler.in(1s, [&] { seen.insert(1); }); + scheduler.in(2s, [&] { seen.insert(2); }); + auto token = scheduler.in(3s, [&] { seen.insert(3); }); + scheduler.at(scheduler.now() + 4s, [&] { seen.insert(4); }); + scheduler.at(scheduler.now() + 8s, [&] { seen.insert(8); }); + + auto start = scheduler.now(); + + // Process first event + EXPECT_TRUE(seen.empty()); + EXPECT_TRUE(scheduler.stepOne()); + EXPECT_TRUE(seen == std::set({1})); + EXPECT_TRUE(scheduler.now() == (start + 1s)); + + // No processing if stepping until current time + EXPECT_TRUE(scheduler.stepUntil(scheduler.now())); + EXPECT_TRUE(seen == std::set({1})); + EXPECT_TRUE(scheduler.now() == (start + 1s)); + + // Process next event + EXPECT_TRUE(scheduler.stepFor(1s)); + EXPECT_TRUE(seen == std::set({1, 2})); + EXPECT_TRUE(scheduler.now() == (start + 2s)); + + // Don't process cancelled event, but advance clock + scheduler.cancel(token); + EXPECT_TRUE(scheduler.stepFor(1s)); + EXPECT_TRUE(seen == std::set({1, 2})); + EXPECT_TRUE(scheduler.now() == (start + 3s)); + + // Process until 3 seen ints + EXPECT_TRUE(scheduler.stepWhile([&]() { return seen.size() < 3; })); + EXPECT_TRUE(seen == std::set({1, 2, 4})); + EXPECT_TRUE(scheduler.now() == (start + 4s)); + + // Process the rest + EXPECT_TRUE(scheduler.step()); + EXPECT_TRUE(seen == std::set({1, 2, 4, 8})); + EXPECT_TRUE(scheduler.now() == (start + 8s)); + + // Process the rest again doesn't advance + EXPECT_TRUE(!scheduler.step()); + EXPECT_TRUE(seen == std::set({1, 2, 4, 8})); + EXPECT_TRUE(scheduler.now() == (start + 8s)); +} + +} // namespace xrpl::test diff --git a/src/test/csf/Scheduler.h b/src/tests/libxrpl/csf/Scheduler.h similarity index 100% rename from src/test/csf/Scheduler.h rename to src/tests/libxrpl/csf/Scheduler.h diff --git a/src/test/csf/Sim.h b/src/tests/libxrpl/csf/Sim.h similarity index 94% rename from src/test/csf/Sim.h rename to src/tests/libxrpl/csf/Sim.h index 94d26d5e06..774537138a 100644 --- a/src/test/csf/Sim.h +++ b/src/tests/libxrpl/csf/Sim.h @@ -1,16 +1,16 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include - #include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include diff --git a/src/test/csf/SimTime.h b/src/tests/libxrpl/csf/SimTime.h similarity index 100% rename from src/test/csf/SimTime.h rename to src/tests/libxrpl/csf/SimTime.h diff --git a/src/test/csf/TrustGraph.h b/src/tests/libxrpl/csf/TrustGraph.h similarity index 99% rename from src/test/csf/TrustGraph.h rename to src/tests/libxrpl/csf/TrustGraph.h index d46a887364..d010b954e0 100644 --- a/src/test/csf/TrustGraph.h +++ b/src/tests/libxrpl/csf/TrustGraph.h @@ -1,9 +1,9 @@ #pragma once -#include - #include +#include + #include #include #include diff --git a/src/test/csf/Tx.h b/src/tests/libxrpl/csf/Tx.h similarity index 100% rename from src/test/csf/Tx.h rename to src/tests/libxrpl/csf/Tx.h diff --git a/src/test/csf/Validation.h b/src/tests/libxrpl/csf/Validation.h similarity index 99% rename from src/test/csf/Validation.h rename to src/tests/libxrpl/csf/Validation.h index 0b9fc94890..4325f96511 100644 --- a/src/test/csf/Validation.h +++ b/src/tests/libxrpl/csf/Validation.h @@ -1,10 +1,10 @@ #pragma once -#include - #include #include +#include + #include #include #include diff --git a/src/test/csf/collectors.h b/src/tests/libxrpl/csf/collectors.h similarity index 99% rename from src/test/csf/collectors.h rename to src/tests/libxrpl/csf/collectors.h index f85854e5dd..05b63f592b 100644 --- a/src/test/csf/collectors.h +++ b/src/tests/libxrpl/csf/collectors.h @@ -1,13 +1,13 @@ #pragma once -#include -#include -#include -#include -#include - #include +#include +#include +#include +#include +#include + #include #include #include diff --git a/src/test/csf/csf_graph.png b/src/tests/libxrpl/csf/csf_graph.png similarity index 100% rename from src/test/csf/csf_graph.png rename to src/tests/libxrpl/csf/csf_graph.png diff --git a/src/test/csf/csf_overview.png b/src/tests/libxrpl/csf/csf_overview.png similarity index 100% rename from src/test/csf/csf_overview.png rename to src/tests/libxrpl/csf/csf_overview.png diff --git a/src/test/csf/events.h b/src/tests/libxrpl/csf/events.h similarity index 96% rename from src/test/csf/events.h rename to src/tests/libxrpl/csf/events.h index 2cf4fd9e9b..4ff15ec987 100644 --- a/src/test/csf/events.h +++ b/src/tests/libxrpl/csf/events.h @@ -1,8 +1,8 @@ #pragma once -#include -#include -#include +#include +#include +#include namespace xrpl::test::csf { diff --git a/src/test/csf/impl/Sim.cpp b/src/tests/libxrpl/csf/impl/Sim.cpp similarity index 93% rename from src/test/csf/impl/Sim.cpp rename to src/tests/libxrpl/csf/impl/Sim.cpp index bf4706927e..28ac5f126a 100644 --- a/src/test/csf/impl/Sim.cpp +++ b/src/tests/libxrpl/csf/impl/Sim.cpp @@ -1,7 +1,7 @@ -#include +#include -#include -#include +#include +#include #include #include diff --git a/src/test/csf/impl/ledgers.cpp b/src/tests/libxrpl/csf/impl/ledgers.cpp similarity index 98% rename from src/test/csf/impl/ledgers.cpp rename to src/tests/libxrpl/csf/impl/ledgers.cpp index 46a3600307..ed1cd927c4 100644 --- a/src/test/csf/impl/ledgers.cpp +++ b/src/tests/libxrpl/csf/impl/ledgers.cpp @@ -1,11 +1,11 @@ -#include - -#include +#include #include #include #include +#include + #include #include #include diff --git a/src/test/csf/ledgers.h b/src/tests/libxrpl/csf/ledgers.h similarity index 99% rename from src/test/csf/ledgers.h rename to src/tests/libxrpl/csf/ledgers.h index 09f5fa54de..ca4e44d5a6 100644 --- a/src/test/csf/ledgers.h +++ b/src/tests/libxrpl/csf/ledgers.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -9,6 +7,8 @@ #include +#include + #include #include #include diff --git a/src/test/csf/random.h b/src/tests/libxrpl/csf/random.h similarity index 98% rename from src/test/csf/random.h rename to src/tests/libxrpl/csf/random.h index f8df253642..007bdecb1b 100644 --- a/src/test/csf/random.h +++ b/src/tests/libxrpl/csf/random.h @@ -135,13 +135,13 @@ class PowerLawDistribution { double xmin_; double a_; - double inv_; + double inv_{1.0 / (1.0 - a_)}; std::uniform_real_distribution uf_{0, 1}; public: using result_type = double; - PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}, inv_(1.0 / (1.0 - a_)) + PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a} { } diff --git a/src/test/csf/submitters.h b/src/tests/libxrpl/csf/submitters.h similarity index 96% rename from src/test/csf/submitters.h rename to src/tests/libxrpl/csf/submitters.h index 160d0bcd9f..4f27f0f665 100644 --- a/src/test/csf/submitters.h +++ b/src/tests/libxrpl/csf/submitters.h @@ -1,8 +1,8 @@ #pragma once -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/test/csf/timers.h b/src/tests/libxrpl/csf/timers.h similarity index 96% rename from src/test/csf/timers.h rename to src/tests/libxrpl/csf/timers.h index 4f13b21b25..e89ea33698 100644 --- a/src/test/csf/timers.h +++ b/src/tests/libxrpl/csf/timers.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index 4abf77f578..20421ab916 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -17,8 +16,6 @@ #include #include #include -#include -#include #include #include @@ -32,6 +29,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -389,7 +389,7 @@ RCLConsensus::Adaptor::onClose( if (!wrongLCL) { LedgerIndex const seq = prevLedger->header().seq + 1; - RCLCensorshipDetector::TxIDSeqVec proposed; + CensorshipDetector::TxIDSeqVec proposed; initialSet->visitLeaves( [&proposed, seq](boost::intrusive_ptr const& item) { diff --git a/src/xrpld/app/consensus/RCLConsensus.h b/src/xrpld/app/consensus/RCLConsensus.h index 4ffe18a7a8..4c07e7e646 100644 --- a/src/xrpld/app/consensus/RCLConsensus.h +++ b/src/xrpld/app/consensus/RCLConsensus.h @@ -1,20 +1,20 @@ #pragma once -#include #include #include #include #include #include #include -#include -#include -#include #include #include #include #include +#include +#include +#include +#include #include #include #include @@ -83,7 +83,7 @@ class RCLConsensus std::atomic prevRoundTime_{std::chrono::milliseconds{0}}; std::atomic mode_{ConsensusMode::Observing}; - RCLCensorshipDetector censorshipDetector_; + CensorshipDetector censorshipDetector_; NegativeUNLVote nUnlVote_; public: diff --git a/src/xrpld/app/consensus/RCLCxPeerPos.h b/src/xrpld/app/consensus/RCLCxPeerPos.h index 050bdf6d36..078556dea8 100644 --- a/src/xrpld/app/consensus/RCLCxPeerPos.h +++ b/src/xrpld/app/consensus/RCLCxPeerPos.h @@ -1,11 +1,10 @@ #pragma once -#include - #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/consensus/RCLValidations.cpp b/src/xrpld/app/consensus/RCLValidations.cpp index 9d40e60b00..c587a04cf0 100644 --- a/src/xrpld/app/consensus/RCLValidations.cpp +++ b/src/xrpld/app/consensus/RCLValidations.cpp @@ -5,12 +5,12 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/consensus/RCLValidations.h b/src/xrpld/app/consensus/RCLValidations.h index 7eadaf0dff..963b6c150e 100644 --- a/src/xrpld/app/consensus/RCLValidations.h +++ b/src/xrpld/app/consensus/RCLValidations.h @@ -1,10 +1,9 @@ #pragma once -#include - #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 4b0091dff6..47cbebb901 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -21,8 +21,6 @@ #include #include #include -#include -#include #include #include #include @@ -54,6 +52,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index c720bdf30b..d7a9a9e449 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -35,6 +34,7 @@ #include #include #include +#include #include #include #include