refactor: Move interval timer into a separate class (#1588)

For #442.
This commit is contained in:
Sergey Kuznetsov
2024-08-07 17:38:24 +01:00
committed by GitHub
parent 27c9e2a530
commit 1b4eed3b2b
30 changed files with 498 additions and 288 deletions

View File

@@ -93,9 +93,9 @@ ClioApplication::run()
boost::asio::io_context ioc{threads};
// Rate limiter, to prevent abuse
auto sweepHandler = web::IntervalSweepHandler{config_, ioc};
auto whitelistHandler = web::WhitelistHandler{config_};
auto dosGuard = web::DOSGuard{config_, whitelistHandler, sweepHandler};
auto dosGuard = web::DOSGuard{config_, whitelistHandler};
auto sweepHandler = web::IntervalSweepHandler{config_, ioc, dosGuard};
// Interface to the database
auto backend = data::make_Backend(config_);

View File

@@ -10,6 +10,7 @@ target_sources(
NetworkValidatedLedgers.cpp
NFTHelpers.cpp
Source.cpp
impl/AmendmentBlockHandler.cpp
impl/ForwardingCache.cpp
impl/ForwardingSource.cpp
impl/GrpcSource.cpp

View File

@@ -22,11 +22,11 @@
#include "data/BackendInterface.hpp"
#include "data/LedgerCache.hpp"
#include "etl/CacheLoader.hpp"
#include "etl/ETLHelpers.hpp"
#include "etl/ETLState.hpp"
#include "etl/LoadBalancer.hpp"
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/SystemState.hpp"
#include "etl/impl/AmendmentBlock.hpp"
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "etl/impl/ExtractionDataPipe.hpp"
#include "etl/impl/Extractor.hpp"
#include "etl/impl/LedgerFetcher.hpp"
@@ -37,7 +37,6 @@
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/json/object.hpp>
#include <grpcpp/grpcpp.h>
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
@@ -85,7 +84,7 @@ class ETLService {
using ExtractorType = etl::impl::Extractor<DataPipeType, LedgerFetcherType>;
using LedgerLoaderType = etl::impl::LedgerLoader<LoadBalancerType, LedgerFetcherType>;
using LedgerPublisherType = etl::impl::LedgerPublisher<CacheType>;
using AmendmentBlockHandlerType = etl::impl::AmendmentBlockHandler<>;
using AmendmentBlockHandlerType = etl::impl::AmendmentBlockHandler;
using TransformerType =
etl::impl::Transformer<DataPipeType, LedgerLoaderType, LedgerPublisherType, AmendmentBlockHandlerType>;

View File

@@ -1,95 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "etl/SystemState.hpp"
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <functional>
namespace etl::impl {
struct AmendmentBlockAction {
void
operator()()
{
static util::Logger const log{"ETL"};
LOG(log.fatal()) << "Can't process new ledgers: The current ETL source is not compatible with the version of "
<< "the libxrpl Clio is currently using. Please upgrade Clio to a newer version.";
}
};
template <typename ActionCallableType = AmendmentBlockAction>
class AmendmentBlockHandler {
std::reference_wrapper<boost::asio::io_context> ctx_;
std::reference_wrapper<SystemState> state_;
boost::asio::steady_timer timer_;
std::chrono::milliseconds interval_;
ActionCallableType action_;
public:
template <typename DurationType = std::chrono::seconds>
AmendmentBlockHandler(
boost::asio::io_context& ioc,
SystemState& state,
DurationType interval = DurationType{1},
ActionCallableType&& action = ActionCallableType()
)
: ctx_{std::ref(ioc)}
, state_{std::ref(state)}
, timer_{ioc}
, interval_{std::chrono::duration_cast<std::chrono::milliseconds>(interval)}
, action_{std::move(action)}
{
}
~AmendmentBlockHandler()
{
boost::asio::post(ctx_.get(), [this]() { timer_.cancel(); });
}
void
onAmendmentBlock()
{
state_.get().isAmendmentBlocked = true;
startReportingTimer();
}
private:
void
startReportingTimer()
{
action_();
timer_.expires_after(interval_);
timer_.async_wait([this](auto ec) {
if (!ec)
boost::asio::post(ctx_.get(), [this] { startReportingTimer(); });
});
}
};
} // namespace etl::impl

View File

@@ -0,0 +1,56 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "etl/SystemState.hpp"
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <chrono>
#include <functional>
#include <utility>
namespace etl::impl {
AmendmentBlockHandler::ActionType const AmendmentBlockHandler::defaultAmendmentBlockAction = []() {
static util::Logger const log{"ETL"};
LOG(log.fatal()) << "Can't process new ledgers: The current ETL source is not compatible with the version of "
<< "the libxrpl Clio is currently using. Please upgrade Clio to a newer version.";
};
AmendmentBlockHandler::AmendmentBlockHandler(
boost::asio::io_context& ioc,
SystemState& state,
std::chrono::steady_clock::duration interval,
ActionType action
)
: state_{std::ref(state)}, repeat_{ioc}, interval_{interval}, action_{std::move(action)}
{
}
void
AmendmentBlockHandler::onAmendmentBlock()
{
state_.get().isAmendmentBlocked = true;
repeat_.start(interval_, action_);
}
} // namespace etl::impl

View File

@@ -0,0 +1,59 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "etl/SystemState.hpp"
#include "util/Repeat.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <functional>
namespace etl::impl {
class AmendmentBlockHandler {
public:
using ActionType = std::function<void()>;
private:
std::reference_wrapper<SystemState> state_;
util::Repeat repeat_;
std::chrono::steady_clock::duration interval_;
ActionType action_;
public:
static ActionType const defaultAmendmentBlockAction;
AmendmentBlockHandler(
boost::asio::io_context& ioc,
SystemState& state,
std::chrono::steady_clock::duration interval = std::chrono::seconds{1},
ActionType action = defaultAmendmentBlockAction
);
void
onAmendmentBlock();
};
} // namespace etl::impl

View File

@@ -23,7 +23,7 @@
#include "data/DBHelpers.hpp"
#include "data/Types.hpp"
#include "etl/SystemState.hpp"
#include "etl/impl/AmendmentBlock.hpp"
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "etl/impl/LedgerLoader.hpp"
#include "util/Assert.hpp"
#include "util/LedgerUtils.hpp"

View File

@@ -14,11 +14,12 @@ target_sources(
prometheus/Prometheus.cpp
Random.cpp
Retry.cpp
SignalsHandler.cpp
Repeat.cpp
requests/RequestBuilder.cpp
requests/Types.cpp
requests/WsConnection.cpp
requests/impl/SslContext.cpp
SignalsHandler.cpp
Taggable.cpp
TerminationHandler.cpp
TimeUtils.cpp

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, the clio developers.
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,17 +17,22 @@
*/
//==============================================================================
#pragma once
#include "util/Repeat.hpp"
#include <cstddef>
#include <functional>
#include <boost/asio/io_context.hpp>
struct FakeAmendmentBlockAction {
std::reference_wrapper<std::size_t> callCount;
namespace util {
void
operator()() const
{
++(callCount.get());
}
};
Repeat::Repeat(boost::asio::io_context& ioc) : timer_(ioc)
{
}
void
Repeat::stop()
{
stopping_ = true;
timer_.cancel();
semaphore_.acquire();
}
} // namespace util

90
src/util/Repeat.hpp Normal file
View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <atomic>
#include <chrono>
#include <concepts>
#include <semaphore>
namespace util {
/**
* @brief A class to repeat some action at a regular interval
* @note io_context must be stopped before the Repeat object is destroyed. Otherwise it is undefined behavior
*/
class Repeat {
boost::asio::steady_timer timer_;
std::atomic_bool stopping_{false};
std::binary_semaphore semaphore_{0};
public:
/**
* @brief Construct a new Repeat object
*
* @param ioc The io_context to use
*/
Repeat(boost::asio::io_context& ioc);
/**
* @brief Stop repeating
* @note This method will block to ensure the repeating is actually stopped. But blocking time should be very short.
*/
void
stop();
/**
* @brief Start asynchronously repeating
*
* @tparam Action The action type
* @param interval The interval to repeat
* @param action The action to call regularly
*/
template <std::invocable Action>
void
start(std::chrono::steady_clock::duration interval, Action&& action)
{
stopping_ = false;
startImpl(interval, std::forward<Action>(action));
}
private:
template <std::invocable Action>
void
startImpl(std::chrono::steady_clock::duration interval, Action&& action)
{
timer_.expires_after(interval);
timer_.async_wait([this, interval, action = std::forward<Action>(action)](auto const&) mutable {
if (stopping_) {
semaphore_.release();
return;
}
action();
startImpl(interval, std::forward<Action>(action));
});
}
};
} // namespace util

View File

@@ -57,11 +57,6 @@ Retry::Retry(RetryStrategyPtr strategy, boost::asio::strand<boost::asio::io_cont
{
}
Retry::~Retry()
{
cancel();
}
void
Retry::cancel()
{

View File

@@ -89,7 +89,6 @@ public:
* @param strand The strand to use for async operations
*/
Retry(RetryStrategyPtr strategy, boost::asio::strand<boost::asio::io_context::executor_type> strand);
~Retry();
/**
* @brief Schedule a retry

View File

@@ -34,11 +34,6 @@ public:
timer_.async_wait(std::forward<decltype(fn)>(fn));
}
~SteadyTimer()
{
cancel();
}
SteadyTimer(SteadyTimer&&) = default;
SteadyTimer(SteadyTimer const&) = delete;

View File

@@ -150,24 +150,15 @@ LogService::init(util::Config const& config)
// get default severity, can be overridden per channel using the `log_channels` array
auto defaultSeverity = config.valueOr<Severity>("log_level", Severity::NFO);
static constexpr std::array<char const*, 7> channels = {
"General",
"WebServer",
"Backend",
"RPC",
"ETL",
"Subscriptions",
"Performance",
};
std::unordered_map<std::string, Severity> min_severity;
for (auto const& channel : channels)
for (auto const& channel : Logger::CHANNELS)
min_severity[channel] = defaultSeverity;
min_severity["Alert"] = Severity::WRN; // Channel for alerts, always warning severity
for (auto const overrides = config.arrayOr("log_channels", {}); auto const& cfg : overrides) {
auto name = cfg.valueOrThrow<std::string>("channel", "Channel name is required");
if (std::count(std::begin(channels), std::end(channels), name) == 0)
if (std::count(std::begin(Logger::CHANNELS), std::end(Logger::CHANNELS), name) == 0)
throw std::runtime_error("Can't override settings for log channel " + name + ": invalid channel");
min_severity[name] = cfg.valueOr<Severity>("log_level", defaultSeverity);

View File

@@ -44,6 +44,7 @@
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp>
#include <array>
#include <cstddef>
#include <optional>
#include <ostream>
@@ -176,6 +177,16 @@ class Logger final {
};
public:
static constexpr std::array<char const*, 7> CHANNELS = {
"General",
"WebServer",
"Backend",
"RPC",
"ETL",
"Subscriptions",
"Performance",
};
/**
* @brief Construct a new Logger object that produces loglines for the
* specified channel.

View File

@@ -58,9 +58,8 @@ public:
* @brief A simple denial of service guard used for rate limiting.
*
* @tparam WhitelistHandlerType The type of the whitelist handler
* @tparam SweepHandlerType The type of the sweep handler
*/
template <typename WhitelistHandlerType, typename SweepHandlerType>
template <typename WhitelistHandlerType>
class BasicDOSGuard : public BaseDOSGuard {
/**
* @brief Accumulated state per IP, state will be reset accordingly
@@ -90,19 +89,13 @@ public:
*
* @param config Clio config
* @param whitelistHandler Whitelist handler that checks whitelist for IP addresses
* @param sweepHandler Sweep handler that implements the sweeping behaviour
*/
BasicDOSGuard(
util::Config const& config,
WhitelistHandlerType const& whitelistHandler,
SweepHandlerType& sweepHandler
)
BasicDOSGuard(util::Config const& config, WhitelistHandlerType const& whitelistHandler)
: whitelistHandler_{std::cref(whitelistHandler)}
, maxFetches_{config.valueOr("dos_guard.max_fetches", DEFAULT_MAX_FETCHES)}
, maxConnCount_{config.valueOr("dos_guard.max_connections", DEFAULT_MAX_CONNECTIONS)}
, maxRequestCount_{config.valueOr("dos_guard.max_requests", DEFAULT_MAX_REQUESTS)}
{
sweepHandler.setup(this);
}
/**
@@ -262,6 +255,6 @@ private:
/**
* @brief A simple denial of service guard used for rate limiting.
*/
using DOSGuard = BasicDOSGuard<web::WhitelistHandler, web::IntervalSweepHandler>;
using DOSGuard = BasicDOSGuard<web::WhitelistHandler>;
} // namespace web

View File

@@ -19,8 +19,6 @@
#include "web/IntervalSweepHandler.hpp"
#include "util/Assert.hpp"
#include "util/Constants.hpp"
#include "util/config/Config.hpp"
#include "web/DOSGuard.hpp"
@@ -30,49 +28,22 @@
#include <boost/system/detail/error_code.hpp>
#include <algorithm>
#include <cstdint>
#include <chrono>
#include <functional>
namespace web {
IntervalSweepHandler::IntervalSweepHandler(util::Config const& config, boost::asio::io_context& ctx)
: sweepInterval_{std::max(
1u,
static_cast<uint32_t>(
config.valueOr("dos_guard.sweep_interval", 1.0) * static_cast<double>(util::MILLISECONDS_PER_SECOND)
)
)}
, ctx_{std::ref(ctx)}
, timer_{ctx.get_executor()}
IntervalSweepHandler::IntervalSweepHandler(
util::Config const& config,
boost::asio::io_context& ctx,
web::BaseDOSGuard& dosGuard
)
: repeat_{std::ref(ctx)}
{
}
IntervalSweepHandler::~IntervalSweepHandler()
{
boost::asio::post(ctx_.get(), [this]() { timer_.cancel(); });
}
void
IntervalSweepHandler::setup(web::BaseDOSGuard* guard)
{
ASSERT(dosGuard_ == nullptr, "Cannot setup DOS guard more than once");
dosGuard_ = guard;
ASSERT(dosGuard_ != nullptr, "DOS guard must be not null");
createTimer();
}
void
IntervalSweepHandler::createTimer()
{
timer_.expires_after(sweepInterval_);
timer_.async_wait([this](boost::system::error_code const& error) {
if (error == boost::asio::error::operation_aborted)
return;
dosGuard_->clear();
boost::asio::post(ctx_.get(), [this] { createTimer(); });
});
auto const sweepInterval{std::max(
std::chrono::milliseconds{1u}, util::Config::toMilliseconds(config.valueOr("dos_guard.sweep_interval", 1.0))
)};
repeat_.start(sweepInterval, [&dosGuard] { dosGuard.clear(); });
}
} // namespace web

View File

@@ -19,28 +19,20 @@
#pragma once
#include "util/Repeat.hpp"
#include "util/config/Config.hpp"
#include <boost/asio.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <functional>
namespace web {
class BaseDOSGuard;
/**
* @brief Sweep handler using a steady_timer and boost::asio::io_context.
* @brief Sweep handler clearing context every sweep interval from config.
*/
class IntervalSweepHandler {
std::chrono::milliseconds sweepInterval_;
std::reference_wrapper<boost::asio::io_context> ctx_;
boost::asio::steady_timer timer_;
web::BaseDOSGuard* dosGuard_ = nullptr;
util::Repeat repeat_;
public:
/**
@@ -48,25 +40,9 @@ public:
*
* @param config Clio config to use
* @param ctx The boost::asio::io_context to use
* @param dosGuard The DOS guard to use
*/
IntervalSweepHandler(util::Config const& config, boost::asio::io_context& ctx);
/**
* @brief Cancels the sweep timer.
*/
~IntervalSweepHandler();
/**
* @brief This setup member function is called by @ref BasicDOSGuard during its initialization.
*
* @param guard Pointer to the dos guard
*/
void
setup(web::BaseDOSGuard* guard);
private:
void
createTimer();
IntervalSweepHandler(util::Config const& config, boost::asio::io_context& ctx, web::BaseDOSGuard& dosGuard);
};
} // namespace web

View File

@@ -2,7 +2,7 @@ add_library(clio_testing_common)
target_sources(
clio_testing_common PRIVATE util/StringUtils.cpp util/TestHttpServer.cpp util/TestWsServer.cpp util/TestObject.cpp
util/AssignRandomPort.cpp
util/AssignRandomPort.cpp util/WithTimeout.cpp
)
include(deps/gtest)

View File

@@ -171,14 +171,4 @@ struct HandlerWithoutInputMock {
MOCK_METHOD(Result, process, (rpc::Context const&), (const));
};
// testing sweep handler by mocking dos guard
template <typename SweepHandler>
struct BasicDOSGuardMock : public web::BaseDOSGuard {
BasicDOSGuardMock(SweepHandler& handler)
{
handler.setup(this);
}
MOCK_METHOD(void, clear, (), (noexcept, override));
};
} // namespace tests::common

View File

@@ -26,6 +26,7 @@
#include <boost/asio/spawn.hpp>
#include <gmock/gmock.h>
#include <chrono>
#include <optional>
#include <thread>
@@ -97,6 +98,13 @@ struct SyncAsioContextTest : virtual public NoLoggerFixture {
ctx.reset();
}
void
runContextFor(std::chrono::milliseconds duration)
{
ctx.run_for(duration);
ctx.reset();
}
protected:
boost::asio::io_context ctx;
};

View File

@@ -22,10 +22,14 @@
#include "util/log/Logger.hpp"
#include <boost/log/core/core.hpp>
#include <boost/log/expressions/predicates/channel_severity_filter.hpp>
#include <boost/log/keywords/format.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <mutex>
#include <ostream>
#include <sstream>
@@ -70,8 +74,14 @@ public:
core->remove_all_sinks();
boost::log::add_console_log(stream_, keywords::format = "%Channel%:%Severity% %Message%");
auto min_severity = expr::channel_severity_filter(util::log_channel, util::log_severity);
std::ranges::for_each(util::Logger::CHANNELS, [&min_severity](char const* channel) {
min_severity[channel] = util::Severity::TRC;
});
min_severity["General"] = util::Severity::DBG;
min_severity["Trace"] = util::Severity::TRC;
core->set_filter(min_severity);
core->set_logging_enabled(true);
}
@@ -89,6 +99,12 @@ protected:
{
ASSERT_TRUE(buffer_.getStrAndReset().empty());
}
std::string
getLoggerString()
{
return buffer_.getStrAndReset();
}
};
/**

View File

@@ -0,0 +1,47 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "util/WithTimeout.hpp"
#include <gtest/gtest.h>
#include <chrono>
#include <cstdlib>
#include <functional>
#include <future>
#include <thread>
namespace tests::common::util {
void
withTimeout(std::chrono::steady_clock::duration timeout, std::function<void()> function)
{
std::promise<void> promise;
auto future = promise.get_future();
std::thread t([&promise, &function] {
function();
promise.set_value();
});
if (future.wait_for(timeout) == std::future_status::timeout) {
FAIL() << "Timeout " << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count() << "ms exceeded";
}
t.join();
}
} // namespace tests::common::util

View File

@@ -0,0 +1,36 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include <chrono>
#include <functional>
namespace tests::common::util {
/**
* @brief Run a function with a timeout. If the function does not complete within the timeout, the test will fail.
*
* @param timeout The timeout duration
* @param function The function to run
*/
void
withTimeout(std::chrono::steady_clock::duration timeout, std::function<void()> function);
} // namespace tests::common::util

View File

@@ -121,6 +121,7 @@ target_sources(
util/requests/WsConnectionTests.cpp
util/RandomTests.cpp
util/RetryTests.cpp
util/RepeatTests.cpp
util/SignalsHandlerTests.cpp
util/TimeUtilsTests.cpp
util/TxUtilTests.cpp
@@ -129,7 +130,7 @@ target_sources(
web/impl/ServerSslContextTests.cpp
web/RPCServerHandlerTests.cpp
web/ServerTests.cpp
web/SweepHandlerTests.cpp
web/IntervalSweepHandlerTests.cpp
web/WhitelistHandlerTests.cpp
# New Config
util/newconfig/ArrayViewTests.cpp

View File

@@ -38,7 +38,6 @@ constexpr auto JSONData = R"JSON(
{
"dos_guard": {
"max_fetches": 100,
"sweep_interval": 1,
"max_connections": 2,
"max_requests": 3,
"whitelist": [
@@ -55,33 +54,13 @@ struct MockWhitelistHandler {
};
using MockWhitelistHandlerType = NiceMock<MockWhitelistHandler>;
class FakeSweepHandler {
private:
using guardType = BasicDOSGuard<MockWhitelistHandlerType, FakeSweepHandler>;
guardType* dosGuard_;
public:
void
setup(guardType* guard)
{
dosGuard_ = guard;
}
void
sweep()
{
dosGuard_->clear();
}
};
}; // namespace
class DOSGuardTest : public NoLoggerFixture {
protected:
Config cfg{json::parse(JSONData)};
FakeSweepHandler sweepHandler{};
MockWhitelistHandlerType whitelistHandler;
BasicDOSGuard<MockWhitelistHandlerType, FakeSweepHandler> guard{cfg, whitelistHandler, sweepHandler};
BasicDOSGuard<MockWhitelistHandlerType> guard{cfg, whitelistHandler};
};
TEST_F(DOSGuardTest, Whitelisting)
@@ -124,7 +103,7 @@ TEST_F(DOSGuardTest, ClearFetchCountOnTimer)
EXPECT_FALSE(guard.add(IP, 1)); // can't add even 1 anymore
EXPECT_FALSE(guard.isOk(IP));
sweepHandler.sweep(); // pretend sweep called from timer
guard.clear(); // pretend sweep called from timer
EXPECT_TRUE(guard.isOk(IP)); // can fetch again
}
@@ -148,6 +127,6 @@ TEST_F(DOSGuardTest, RequestLimitOnTimer)
EXPECT_TRUE(guard.isOk(IP));
EXPECT_FALSE(guard.request(IP));
EXPECT_FALSE(guard.isOk(IP));
sweepHandler.sweep();
guard.clear();
EXPECT_TRUE(guard.isOk(IP)); // can request again
}

View File

@@ -18,37 +18,42 @@
//==============================================================================
#include "etl/SystemState.hpp"
#include "etl/impl/AmendmentBlock.hpp"
#include "util/FakeAmendmentBlockAction.hpp"
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "util/AsioContextTestFixture.hpp"
#include "util/LoggerFixtures.hpp"
#include "util/MockPrometheus.hpp"
#include <boost/asio/io_context.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <chrono>
#include <cstddef>
#include <functional>
using namespace testing;
using namespace etl;
using namespace etl::impl;
struct AmendmentBlockHandlerTest : util::prometheus::WithPrometheus, NoLoggerFixture {
using AmendmentBlockHandlerType = impl::AmendmentBlockHandler<FakeAmendmentBlockAction>;
boost::asio::io_context ioc_;
struct AmendmentBlockHandlerTest : util::prometheus::WithPrometheus, SyncAsioContextTest {
testing::StrictMock<testing::MockFunction<void()>> actionMock;
etl::SystemState state;
};
TEST_F(AmendmentBlockHandlerTest, CallToOnAmendmentBlockSetsStateAndRepeatedlyCallsAction)
{
std::size_t callCount = 0;
SystemState state;
AmendmentBlockHandlerType handler{ioc_, state, std::chrono::nanoseconds{1}, {std::ref(callCount)}};
AmendmentBlockHandler handler{ctx, state, std::chrono::nanoseconds{1}, actionMock.AsStdFunction()};
EXPECT_FALSE(state.isAmendmentBlocked);
EXPECT_CALL(actionMock, Call()).Times(testing::AtLeast(10));
handler.onAmendmentBlock();
EXPECT_TRUE(state.isAmendmentBlocked);
ioc_.run_for(std::chrono::milliseconds{1});
EXPECT_TRUE(callCount >= 10);
runContextFor(std::chrono::milliseconds{1});
}
struct DefaultAmendmentBlockActionTest : LoggerFixture {};
TEST_F(DefaultAmendmentBlockActionTest, Call)
{
AmendmentBlockHandler::defaultAmendmentBlockAction();
auto const loggerString = getLoggerString();
EXPECT_TRUE(loggerString.starts_with("ETL:FTL Can't process new ledgers")) << "LoggerString " << loggerString;
}

View File

@@ -0,0 +1,82 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "util/AsioContextTestFixture.hpp"
#include "util/Repeat.hpp"
#include "util/WithTimeout.hpp"
#include <boost/asio/error.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <chrono>
#include <functional>
#include <ranges>
#include <thread>
#include <utility>
using namespace util;
using testing::AtLeast;
struct RepeatTests : SyncAsioContextTest {
Repeat repeat{ctx};
testing::StrictMock<testing::MockFunction<void()>> handlerMock;
void
withRunningContext(std::function<void()> func)
{
tests::common::util::withTimeout(std::chrono::seconds{1000}, [this, func = std::move(func)]() {
auto workGuard = boost::asio::make_work_guard(ctx);
std::thread thread{[this]() { ctx.run(); }};
func();
workGuard.reset();
thread.join();
});
}
};
TEST_F(RepeatTests, CallsHandler)
{
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
EXPECT_CALL(handlerMock, Call).Times(AtLeast(10));
runContextFor(std::chrono::milliseconds{20});
}
TEST_F(RepeatTests, StopsOnStop)
{
withRunningContext([this]() {
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
EXPECT_CALL(handlerMock, Call).Times(AtLeast(1));
std::this_thread::sleep_for(std::chrono::milliseconds{10});
repeat.stop();
});
}
TEST_F(RepeatTests, RunsAfterStop)
{
withRunningContext([this]() {
for ([[maybe_unused]] auto _ : std::ranges::iota_view(0, 2)) {
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
EXPECT_CALL(handlerMock, Call).Times(AtLeast(1));
std::this_thread::sleep_for(std::chrono::milliseconds{10});
repeat.stop();
}
});
}

View File

@@ -17,9 +17,9 @@
*/
//==============================================================================
#include "rpc/FakesAndMocks.hpp"
#include "util/AsioContextTestFixture.hpp"
#include "util/config/Config.hpp"
#include "web/DOSGuard.hpp"
#include "web/IntervalSweepHandler.hpp"
#include <boost/json/parse.hpp>
@@ -28,30 +28,29 @@
#include <chrono>
using namespace util;
using namespace web;
using namespace testing;
constexpr static auto JSONData = R"JSON(
struct IntervalSweepHandlerTest : SyncAsioContextTest {
protected:
constexpr static auto JSONData = R"JSON(
{
"dos_guard": {
"max_fetches": 100,
"sweep_interval": 0.1,
"max_connections": 2,
"whitelist": ["127.0.0.1"]
"sweep_interval": 0
}
}
)JSON";
class DOSGuardIntervalSweepHandlerTest : public SyncAsioContextTest {
protected:
Config cfg{boost::json::parse(JSONData)};
IntervalSweepHandler sweepHandler{cfg, ctx};
tests::common::BasicDOSGuardMock<IntervalSweepHandler> guard{sweepHandler};
struct DosGuardMock : BaseDOSGuard {
MOCK_METHOD(void, clear, (), (noexcept, override));
};
testing::StrictMock<DosGuardMock> guardMock;
util::Config cfg{boost::json::parse(JSONData)};
IntervalSweepHandler sweepHandler{cfg, ctx, guardMock};
};
TEST_F(DOSGuardIntervalSweepHandlerTest, SweepAfterInterval)
TEST_F(IntervalSweepHandlerTest, SweepAfterInterval)
{
EXPECT_CALL(guard, clear()).Times(AtLeast(2));
ctx.run_for(std::chrono::milliseconds(400));
EXPECT_CALL(guardMock, clear()).Times(testing::AtLeast(10));
runContextFor(std::chrono::milliseconds{20});
}

View File

@@ -127,14 +127,14 @@ struct WebServerTest : NoLoggerFixture {
boost::asio::io_context ctxSync;
std::string const port = std::to_string(tests::util::generateFreePort());
Config cfg{generateJSONWithDynamicPort(port)};
IntervalSweepHandler sweepHandler = web::IntervalSweepHandler{cfg, ctxSync};
WhitelistHandler whitelistHandler = web::WhitelistHandler{cfg};
DOSGuard dosGuard = web::DOSGuard{cfg, whitelistHandler, sweepHandler};
WhitelistHandler whitelistHandler{cfg};
DOSGuard dosGuard{cfg, whitelistHandler};
IntervalSweepHandler sweepHandler{cfg, ctxSync, dosGuard};
Config cfgOverload{generateJSONDataOverload(port)};
IntervalSweepHandler sweepHandlerOverload = web::IntervalSweepHandler{cfgOverload, ctxSync};
WhitelistHandler whitelistHandlerOverload = web::WhitelistHandler{cfgOverload};
DOSGuard dosGuardOverload = web::DOSGuard{cfgOverload, whitelistHandlerOverload, sweepHandlerOverload};
WhitelistHandler whitelistHandlerOverload{cfgOverload};
DOSGuard dosGuardOverload{cfgOverload, whitelistHandlerOverload};
IntervalSweepHandler sweepHandlerOverload{cfgOverload, ctxSync, dosGuardOverload};
// this ctx is for http server
boost::asio::io_context ctx;