#include #include #include #include #include #include #include #include #include #include #include namespace xrpl::test { class HashRouter_test : public beast::unit_test::Suite { static 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 const key1(HashRouterFlags::PRIVATE1); HashRouterFlags const key2(HashRouterFlags::PRIVATE2); HashRouterFlags const key3(HashRouterFlags::PRIVATE3); auto const ukey1 = uint256{static_cast(key1)}; auto const ukey2 = uint256{static_cast(key2)}; auto const ukey3 = uint256{static_cast(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 const key1(HashRouterFlags::PRIVATE1); HashRouterFlags const key2(HashRouterFlags::PRIVATE2); HashRouterFlags const key3(HashRouterFlags::PRIVATE3); HashRouterFlags const key4(HashRouterFlags::PRIVATE4); auto const ukey1 = uint256{static_cast(key1)}; auto const ukey2 = uint256{static_cast(key2)}; auto const ukey3 = uint256{static_cast(key3)}; auto const ukey4 = uint256{static_cast(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> 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->empty()); } void testProcess() { testcase("Process"); using namespace std::chrono_literals; TestStopwatch stopwatch; HashRouter router(getSetup(5s, 1s), stopwatch); uint256 const key(1); HashRouter::PeerShortID const peer = 1; HashRouterFlags flags = HashRouterFlags::UNDEFINED; 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 const cfg; // default auto const setup = setupHashRouter(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 = setupHashRouter(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 = setupHashRouter(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 { setupHashRouter(cfg); fail(); } catch (std::exception const& e) { std::string const 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 { setupHashRouter(cfg); fail(); } catch (std::exception const& e) { std::string const 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 { setupHashRouter(cfg); fail(); } catch (std::exception const& e) { std::string const 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 = setupHashRouter(cfg); // The set function ignores values that don't convert, 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 const f1 = HF::BAD; HF const f2 = HF::SAVED; HF const combined = f1 | f2; BEAST_EXPECT(static_cast(combined) == (static_cast(f1) | static_cast(f2))); HF temp = f1; temp |= f2; BEAST_EXPECT(temp == combined); HF const 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, xrpl); } // namespace xrpl::test