Files
rippled/src/test/app/HashRouter_test.cpp
Bart 1d42c4f6de refactor: Remove unnecessary copyright notices already covered by LICENSE.md (#5929)
Per XLS-0095, we are taking steps to rename ripple(d) to xrpl(d).

This change specifically removes all copyright notices referencing Ripple, XRPLF, and certain affiliated contributors upon mutual agreement, so the notice in the LICENSE.md file applies throughout. Copyright notices referencing external contributions remain as-is. Duplicate verbiage is also removed.
2025-11-04 08:33:42 +00:00

411 lines
13 KiB
C++

#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/core/Config.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/unit_test.h>
namespace ripple {
namespace test {
class HashRouter_test : public beast::unit_test::suite
{
HashRouter::Setup
getSetup(std::chrono::seconds hold, std::chrono::seconds relay)
{
HashRouter::Setup setup;
setup.holdTime = hold;
setup.relayTime = relay;
return setup;
}
void
testNonExpiration()
{
testcase("Non-expiration");
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch);
HashRouterFlags key1(HashRouterFlags::PRIVATE1);
HashRouterFlags key2(HashRouterFlags::PRIVATE2);
HashRouterFlags key3(HashRouterFlags::PRIVATE3);
auto const ukey1 = uint256{static_cast<std::uint64_t>(key1)};
auto const ukey2 = uint256{static_cast<std::uint64_t>(key2)};
auto const ukey3 = uint256{static_cast<std::uint64_t>(key3)};
// t=0
router.setFlags(ukey1, HashRouterFlags::PRIVATE1);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
router.setFlags(ukey2, HashRouterFlags::PRIVATE2);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE2);
// key1 : 0
// key2 : 0
// key3: null
++stopwatch;
// Because we are accessing key1 here, it
// will NOT be expired for another two ticks
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
// key1 : 1
// key2 : 0
// key3 null
++stopwatch;
// t=3
router.setFlags(ukey3, HashRouterFlags::PRIVATE3); // force expiration
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED);
}
void
testExpiration()
{
testcase("Expiration");
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch);
HashRouterFlags key1(HashRouterFlags::PRIVATE1);
HashRouterFlags key2(HashRouterFlags::PRIVATE2);
HashRouterFlags key3(HashRouterFlags::PRIVATE3);
HashRouterFlags key4(HashRouterFlags::PRIVATE4);
auto const ukey1 = uint256{static_cast<std::uint64_t>(key1)};
auto const ukey2 = uint256{static_cast<std::uint64_t>(key2)};
auto const ukey3 = uint256{static_cast<std::uint64_t>(key3)};
auto const ukey4 = uint256{static_cast<std::uint64_t>(key4)};
BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4);
// t=0
router.setFlags(ukey1, HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD);
// key1 : 0
// key2 : null
// key3 : null
++stopwatch;
// Expiration is triggered by insertion,
// and timestamps are updated on access,
// so key1 will be expired after the second
// call to setFlags.
// t=1
router.setFlags(ukey2, HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
// key1 : 1
// key2 : 1
// key3 : null
++stopwatch;
// t=2
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
// key1 : 1
// key2 : 2
// key3 : null
++stopwatch;
// t=3
router.setFlags(ukey3, HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD);
// key1 : 3
// key2 : 3
// key3 : 3
++stopwatch;
// t=4
// No insertion, no expiration
router.setFlags(ukey1, HashRouterFlags::SAVED);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::SAVED);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD);
// key1 : 4
// key2 : 4
// key3 : 4
++stopwatch;
++stopwatch;
// t=6
router.setFlags(ukey4, HashRouterFlags::TRUSTED);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey4) == HashRouterFlags::TRUSTED);
// key1 : 6
// key2 : 6
// key3 : 6
// key4 : 6
}
void
testSuppression()
{
testcase("Suppression");
// Normal HashRouter
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1);
uint256 const key2(2);
uint256 const key3(3);
uint256 const key4(4);
BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4);
HashRouterFlags flags(HashRouterFlags::BAD); // This value is ignored
router.addSuppression(key1);
BEAST_EXPECT(router.addSuppressionPeer(key2, 15));
BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags));
BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED);
++stopwatch;
BEAST_EXPECT(!router.addSuppressionPeer(key1, 2));
BEAST_EXPECT(!router.addSuppressionPeer(key2, 3));
BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags));
BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.addSuppressionPeer(key4, 5));
}
void
testSetFlags()
{
testcase("Set Flags");
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1);
BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE1));
BEAST_EXPECT(!router.setFlags(key1, HashRouterFlags::PRIVATE1));
BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE2));
}
void
testRelay()
{
testcase("Relay");
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(getSetup(50s, 1s), stopwatch);
uint256 const key1(1);
std::optional<std::set<HashRouter::PeerShortID>> peers;
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->empty());
router.addSuppressionPeer(key1, 1);
router.addSuppressionPeer(key1, 3);
router.addSuppressionPeer(key1, 5);
// No action, because relayed
BEAST_EXPECT(!router.shouldRelay(key1));
// Expire, but since the next search will
// be for this entry, it will get refreshed
// instead. However, the relay won't.
++stopwatch;
// Get those peers we added earlier
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->size() == 3);
router.addSuppressionPeer(key1, 2);
router.addSuppressionPeer(key1, 4);
// No action, because relayed
BEAST_EXPECT(!router.shouldRelay(key1));
// Expire, but since the next search will
// be for this entry, it will get refreshed
// instead. However, the relay won't.
++stopwatch;
// Relay again
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->size() == 2);
// Expire again
++stopwatch;
// Confirm that peers list is empty.
peers = router.shouldRelay(key1);
BEAST_EXPECT(peers && peers->size() == 0);
}
void
testProcess()
{
testcase("Process");
using namespace std::chrono_literals;
TestStopwatch stopwatch;
HashRouter router(getSetup(5s, 1s), stopwatch);
uint256 const key(1);
HashRouter::PeerShortID peer = 1;
HashRouterFlags flags;
BEAST_EXPECT(router.shouldProcess(key, peer, flags, 1s));
BEAST_EXPECT(!router.shouldProcess(key, peer, flags, 1s));
++stopwatch;
++stopwatch;
BEAST_EXPECT(router.shouldProcess(key, peer, flags, 1s));
}
void
testSetup()
{
testcase("setup_HashRouter");
using namespace std::chrono_literals;
{
Config cfg;
// default
auto const setup = setup_HashRouter(cfg);
BEAST_EXPECT(setup.holdTime == 300s);
BEAST_EXPECT(setup.relayTime == 30s);
}
{
Config cfg;
// non-default
auto& h = cfg.section("hashrouter");
h.set("hold_time", "600");
h.set("relay_time", "15");
auto const setup = setup_HashRouter(cfg);
BEAST_EXPECT(setup.holdTime == 600s);
BEAST_EXPECT(setup.relayTime == 15s);
}
{
Config cfg;
// equal
auto& h = cfg.section("hashrouter");
h.set("hold_time", "400");
h.set("relay_time", "400");
auto const setup = setup_HashRouter(cfg);
BEAST_EXPECT(setup.holdTime == 400s);
BEAST_EXPECT(setup.relayTime == 400s);
}
{
Config cfg;
// wrong order
auto& h = cfg.section("hashrouter");
h.set("hold_time", "60");
h.set("relay_time", "120");
try
{
setup_HashRouter(cfg);
fail();
}
catch (std::exception const& e)
{
std::string expected =
"HashRouter relay time must be less than or equal to hold "
"time";
BEAST_EXPECT(e.what() == expected);
}
}
{
Config cfg;
// too small hold
auto& h = cfg.section("hashrouter");
h.set("hold_time", "10");
h.set("relay_time", "120");
try
{
setup_HashRouter(cfg);
fail();
}
catch (std::exception const& e)
{
std::string expected =
"HashRouter hold time must be at least 12 seconds (the "
"approximate validation time for three "
"ledgers).";
BEAST_EXPECT(e.what() == expected);
}
}
{
Config cfg;
// too small relay
auto& h = cfg.section("hashrouter");
h.set("hold_time", "500");
h.set("relay_time", "6");
try
{
setup_HashRouter(cfg);
fail();
}
catch (std::exception const& e)
{
std::string expected =
"HashRouter relay time must be at least 8 seconds (the "
"approximate validation time for two ledgers).";
BEAST_EXPECT(e.what() == expected);
}
}
{
Config cfg;
// garbage
auto& h = cfg.section("hashrouter");
h.set("hold_time", "alice");
h.set("relay_time", "bob");
auto const setup = setup_HashRouter(cfg);
// The set function ignores values that don't covert, so the
// defaults are left unchanged
BEAST_EXPECT(setup.holdTime == 300s);
BEAST_EXPECT(setup.relayTime == 30s);
}
}
void
testFlagsOps()
{
testcase("Bitwise Operations");
using HF = HashRouterFlags;
using UHF = std::underlying_type_t<HF>;
HF f1 = HF::BAD;
HF f2 = HF::SAVED;
HF combined = f1 | f2;
BEAST_EXPECT(
static_cast<UHF>(combined) ==
(static_cast<UHF>(f1) | static_cast<UHF>(f2)));
HF temp = f1;
temp |= f2;
BEAST_EXPECT(temp == combined);
HF intersect = combined & f1;
BEAST_EXPECT(intersect == f1);
HF temp2 = combined;
temp2 &= f1;
BEAST_EXPECT(temp2 == f1);
BEAST_EXPECT(any(f1));
BEAST_EXPECT(any(f2));
BEAST_EXPECT(any(combined));
BEAST_EXPECT(!any(HF::UNDEFINED));
}
public:
void
run() override
{
testNonExpiration();
testExpiration();
testSuppression();
testSetFlags();
testRelay();
testProcess();
testSetup();
testFlagsOps();
}
};
BEAST_DEFINE_TESTSUITE(HashRouter, app, ripple);
} // namespace test
} // namespace ripple