feat: Dosguard API weights (#2082)

Experimental support for Dosguard API weights.
This commit is contained in:
Sergey Kuznetsov
2025-05-13 15:02:21 +01:00
committed by GitHub
parent d02da5d28a
commit b87b32db86
42 changed files with 1653 additions and 329 deletions

View File

@@ -29,11 +29,13 @@
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/Types.hpp"
#include "web/SubscriptionContextInterface.hpp"
#include "web/dosguard/DOSGuardMock.hpp"
#include "web/ng/MockConnection.hpp"
#include "web/ng/RPCServerHandler.hpp"
#include "web/ng/Request.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/beast/core/buffers_to_string.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/status.hpp>
#include <boost/beast/http/string_body.hpp>
@@ -72,23 +74,84 @@ protected:
std::shared_ptr<testing::StrictMock<MockRPCEngine>> rpcEngine_ =
std::make_shared<testing::StrictMock<MockRPCEngine>>();
std::shared_ptr<StrictMock<MockETLService>> etl_ = std::make_shared<StrictMock<MockETLService>>();
RPCServerHandler<MockRPCEngine> rpcServerHandler_{config, backend_, rpcEngine_, etl_};
DOSGuardStrictMock dosguard_;
RPCServerHandler<MockRPCEngine> rpcServerHandler_{config, backend_, rpcEngine_, etl_, dosguard_};
util::TagDecoratorFactory tagFactory_{config};
StrictMockConnectionMetadata connectionMetadata_{"some ip", tagFactory_};
std::string const ip_ = "some ip";
StrictMockConnectionMetadata connectionMetadata_{ip_, tagFactory_};
Request::HttpHeaders const httpHeaders_;
static Request
makeHttpRequest(std::string_view body)
{
return Request{http::request<http::string_body>{http::verb::post, "/", 11, body}};
}
Request
makeWsRequest(std::string body)
{
return Request{std::move(body), httpHeaders_};
}
};
TEST_F(NgRpcServerHandlerTest, DosguardRejectedHttpRequest)
{
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest("some message");
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(false));
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
auto const responseHttp = std::move(response).intoHttpResponse();
EXPECT_EQ(responseHttp.result(), http::status::service_unavailable);
auto const responseJson = boost::json::parse(responseHttp.body()).as_object();
EXPECT_EQ(responseJson.at("error_code").as_int64(), rpc::RippledError::rpcSLOW_DOWN);
});
}
TEST_F(NgRpcServerHandlerTest, DosguardRejectedWsRequest)
{
runSpawn([&](boost::asio::yield_context yield) {
auto const requestStr = "some message";
auto const request = makeWsRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(false));
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
auto const responseWs = boost::beast::buffers_to_string(response.asWsResponse());
auto const responseJson = boost::json::parse(responseWs).as_object();
EXPECT_EQ(responseJson.at("error_code").as_int64(), rpc::RippledError::rpcSLOW_DOWN);
EXPECT_EQ(responseJson.at("request").as_string(), requestStr);
});
}
TEST_F(NgRpcServerHandlerTest, DosguardRejectedWsJsonRequest)
{
runSpawn([&](boost::asio::yield_context yield) {
auto const requestStr = R"json({"request": "some message", "id": "some id"})json";
auto const request = makeWsRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(false));
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
auto const responseWs = boost::beast::buffers_to_string(response.asWsResponse());
auto const responseJson = boost::json::parse(responseWs).as_object();
EXPECT_EQ(responseJson.at("error_code").as_int64(), rpc::RippledError::rpcSLOW_DOWN);
EXPECT_EQ(responseJson.at("request").as_string(), requestStr);
EXPECT_EQ(responseJson.at("id").as_string(), "some id");
});
}
TEST_F(NgRpcServerHandlerTest, PostToRpcEngineFailed)
{
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest("some message");
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce(Return(false));
EXPECT_CALL(*rpcEngine_, notifyTooBusy());
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
@@ -107,6 +170,8 @@ TEST_F(NgRpcServerHandlerTest, CoroutineSleepsUntilRpcEngineFinishes)
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest("some message");
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
boost::asio::spawn(
ctx_,
@@ -131,6 +196,8 @@ TEST_F(NgRpcServerHandlerTest, JsonParseFailed)
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest("not a json");
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(*rpcEngine_, notifyBadSyntax);
fn(yield);
@@ -141,10 +208,61 @@ TEST_F(NgRpcServerHandlerTest, JsonParseFailed)
});
}
TEST_F(NgRpcServerHandlerTest, DosguardRejectedParsedRequest)
{
runSpawn([&](boost::asio::yield_context yield) {
std::string const requestStr = "{}";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(false));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
fn(yield);
return true;
});
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
auto const responseHttp = std::move(response).intoHttpResponse();
EXPECT_EQ(responseHttp.result(), http::status::service_unavailable);
auto const responseJson = boost::json::parse(responseHttp.body()).as_object();
EXPECT_EQ(responseJson.at("error_code").as_int64(), rpc::RippledError::rpcSLOW_DOWN);
});
}
TEST_F(NgRpcServerHandlerTest, DosguardAddsLoadWarning)
{
runSpawn([&](boost::asio::yield_context yield) {
std::string const requestStr = "{}";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(false));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
fn(yield);
return true;
});
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(false));
auto response = rpcServerHandler_(request, connectionMetadata_, nullptr, yield);
auto const responseHttp = std::move(response).intoHttpResponse();
EXPECT_EQ(responseHttp.result(), http::status::service_unavailable);
auto const responseJson = boost::json::parse(responseHttp.body()).as_object();
EXPECT_EQ(responseJson.at("error_code").as_int64(), rpc::RippledError::rpcSLOW_DOWN);
EXPECT_EQ(responseJson.at("warning").as_string(), "load");
EXPECT_EQ(responseJson.at("warnings").as_array().at(0).as_object().at("id").as_int64(), rpc::WarnRpcRateLimit);
});
}
TEST_F(NgRpcServerHandlerTest, GotNotJsonObject)
{
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest("[]");
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(*rpcEngine_, notifyBadSyntax);
fn(yield);
@@ -158,8 +276,12 @@ TEST_F(NgRpcServerHandlerTest, GotNotJsonObject)
TEST_F(NgRpcServerHandlerTest, HandleRequest_NoRangeFromBackend)
{
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest("{}");
std::string const requestStr = "{}";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillOnce(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, notifyNotReady);
@@ -180,8 +302,12 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_ContextCreationFailed)
{
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest("{}");
std::string const requestStr = "{}";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, notifyBadSyntax);
@@ -200,8 +326,12 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_BuildResponseFailed)
{
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
std::string const requestStr = R"json({"method":"some_method"})json";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse)
@@ -227,8 +357,12 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_BuildResponseThrewAnException)
{
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
std::string const requestStr = R"json({"method":"some_method"})json";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse).WillOnce([](auto&&) -> rpc::Result {
@@ -249,8 +383,12 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_Successful_HttpRequest)
{
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
std::string const requestStr = R"json({"method":"some_method"})json";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse)
@@ -278,8 +416,12 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_OutdatedWarning)
{
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
std::string const requestStr = R"json({"method":"some_method"})json";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse)
@@ -313,8 +455,12 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_Successful_HttpRequest_Forwarded)
{
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
std::string const requestStr = R"json({"method":"some_method"})json";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse)
@@ -345,8 +491,12 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_Successful_HttpRequest_HasError)
{
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
auto const request = makeHttpRequest(R"json({"method":"some_method"})json");
std::string const requestStr = R"json({"method":"some_method"})json";
auto const request = makeHttpRequest(requestStr);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse)
@@ -393,8 +543,12 @@ TEST_F(NgRpcServerHandlerWsTest, HandleRequest_Successful_WsRequest)
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
Request::HttpHeaders const headers;
auto const request = Request(R"json({"method":"some_method", "id": 1234, "api_version": 1})json", headers);
std::string const requestStr = R"json({"method":"some_method", "id": 1234, "api_version": 1})json";
auto const request = Request(requestStr, headers);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse)
@@ -424,8 +578,12 @@ TEST_F(NgRpcServerHandlerWsTest, HandleRequest_Successful_WsRequest_HasError)
backend_->setRange(0, 1);
runSpawn([&](boost::asio::yield_context yield) {
Request::HttpHeaders const headers;
auto const request = Request(R"json({"method":"some_method", "id": 1234, "api_version": 1})json", headers);
std::string const requestStr = R"json({"method":"some_method", "id": 1234, "api_version": 1})json";
auto const request = Request(requestStr, headers);
EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));
EXPECT_CALL(dosguard_, request(ip_, boost::json::parse(requestStr).as_object())).WillOnce(Return(true));
EXPECT_CALL(dosguard_, add(ip_, testing::_)).WillOnce(Return(true));
EXPECT_CALL(*rpcEngine_, post).WillOnce([&](auto&& fn, auto&&) {
EXPECT_CALL(connectionMetadata_, wasUpgraded).WillRepeatedly(Return(not request.isHttp()));
EXPECT_CALL(*rpcEngine_, buildResponse)