feat: add 'force_forward' field to request (#1647)

Fix #1141
This commit is contained in:
cyan317
2024-09-17 11:42:51 +01:00
committed by GitHub
parent bea905adcd
commit 0282504f18
2 changed files with 159 additions and 221 deletions

View File

@@ -69,6 +69,9 @@ public:
if (specifiesCurrentOrClosedLedger(request))
return true;
if (isForcedForward(ctx))
return true;
auto const checkAccountInfoForward = [&]() {
return ctx.method == "account_info" and request.contains("queue") and request.at("queue").is_bool() and
request.at("queue").as_bool();
@@ -138,6 +141,14 @@ private:
{
return handlerProvider_->contains(method) || isProxied(method);
}
bool
isForcedForward(web::Context const& ctx) const
{
static constexpr auto FORCE_FORWARD = "force_forward";
return ctx.isAdmin and ctx.params.contains(FORCE_FORWARD) and ctx.params.at(FORCE_FORWARD).is_bool() and
ctx.params.at(FORCE_FORWARD).as_bool();
}
};
} // namespace rpc::impl

View File

@@ -23,6 +23,7 @@
#include "util/MockCounters.hpp"
#include "util/MockHandlerProvider.hpp"
#include "util/MockLoadBalancer.hpp"
#include "util/NameGenerator.hpp"
#include "util/Taggable.hpp"
#include "util/config/Config.hpp"
#include "web/Context.hpp"
@@ -32,10 +33,12 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>
using namespace rpc;
using namespace testing;
@@ -59,235 +62,159 @@ protected:
};
};
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfClioOnly)
struct ShouldForwardParamTestCaseBundle {
std::string testName;
std::uint32_t apiVersion;
std::string method;
std::string testJson;
bool mockedIsClioOnly;
std::uint32_t called;
bool isAdmin;
bool expected;
};
struct ShouldForwardParameterTest : public RPCForwardingProxyTest,
WithParamInterface<ShouldForwardParamTestCaseBundle> {};
static auto
generateTestValuesForParametersTest()
{
auto const isClioOnly = true;
auto const isAdmin = true;
auto const shouldForward = true;
return std::vector<ShouldForwardParamTestCaseBundle>{
{"ShouldForwardReturnsFalseIfClioOnly", 2u, "test", "{}", isClioOnly, 1, !isAdmin, !shouldForward},
{"ShouldForwardReturnsTrueIfProxied", 2u, "submit", "{}", !isClioOnly, 1, !isAdmin, shouldForward},
{"ShouldForwardReturnsTrueIfCurrentLedgerSpecified",
2u,
"anymethod",
R"({"ledger_index": "current"})",
!isClioOnly,
1,
!isAdmin,
shouldForward},
{"ShouldForwardReturnsTrueIfClosedLedgerSpecified",
2u,
"anymethod",
R"({"ledger_index": "closed"})",
!isClioOnly,
1,
!isAdmin,
shouldForward},
{"ShouldForwardReturnsTrueIfAccountInfoWithQueueSpecified",
2u,
"account_info",
R"({"queue": true})",
!isClioOnly,
1,
!isAdmin,
shouldForward},
{"ShouldForwardReturnsFalseIfAccountInfoQueueIsFalse",
2u,
"account_info",
R"({"queue": false})",
!isClioOnly,
1,
!isAdmin,
!shouldForward},
{"ShouldForwardReturnsTrueIfLedgerWithQueueSpecified",
2u,
"ledger",
R"({"queue": true})",
!isClioOnly,
1,
!isAdmin,
shouldForward},
{"ShouldForwardReturnsFalseIfLedgerQueueIsFalse",
2u,
"ledger",
R"({"queue": false})",
!isClioOnly,
1,
!isAdmin,
!shouldForward},
{"ShouldNotForwardReturnsTrueIfAPIVersionIsV1",
1u,
"api_version_check",
"{}",
!isClioOnly,
1,
!isAdmin,
!shouldForward},
{"ShouldForwardReturnsFalseIfAPIVersionIsV2",
2u,
"api_version_check",
"{}",
!isClioOnly,
1,
!isAdmin,
!shouldForward},
{"ShouldNeverForwardSubscribe", 1u, "subscribe", "{}", !isClioOnly, 0, !isAdmin, !shouldForward},
{"ShouldNeverForwardUnsubscribe", 1u, "unsubscribe", "{}", !isClioOnly, 0, !isAdmin, !shouldForward},
{"ForceForwardTrue", 1u, "any_method", R"({"force_forward": true})", !isClioOnly, 1, isAdmin, shouldForward},
{"ForceForwardFalse", 1u, "any_method", R"({"force_forward": false})", !isClioOnly, 1, isAdmin, !shouldForward},
{"ForceForwardNotAdmin",
1u,
"any_method",
R"({"force_forward": true})",
!isClioOnly,
1,
!isAdmin,
!shouldForward},
{"ForceForwardSubscribe",
1u,
"subscribe",
R"({"force_forward": true})",
!isClioOnly,
0,
isAdmin,
not shouldForward},
{"ForceForwardUnsubscribe",
1u,
"unsubscribe",
R"({"force_forward": true})",
!isClioOnly,
0,
isAdmin,
!shouldForward},
{"ForceForwardClioOnly",
1u,
"clio_only_method",
R"({"force_forward": true})",
isClioOnly,
1,
isAdmin,
!shouldForward},
};
}
INSTANTIATE_TEST_CASE_P(
ShouldForwardTest,
ShouldForwardParameterTest,
ValuesIn(generateTestValuesForParametersTest()),
tests::util::NameGenerator
);
TEST_P(ShouldForwardParameterTest, Test)
{
auto const testBundle = GetParam();
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "test";
auto const params = json::parse("{}");
auto const apiVersion = testBundle.apiVersion;
auto const method = testBundle.method;
auto const params = json::parse(testBundle.testJson);
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(true));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(testBundle.mockedIsClioOnly));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(testBundle.called);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const ctx = web::Context(
yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, testBundle.isAdmin
);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfProxied)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "submit";
auto const params = json::parse("{}");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_TRUE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfCurrentLedgerSpecified)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "anymethod";
auto const params = json::parse(R"({"ledger_index": "current"})");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_TRUE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfClosedLedgerSpecified)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "anymethod";
auto const params = json::parse(R"({"ledger_index": "closed"})");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_TRUE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfAccountInfoWithQueueSpecified)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "account_info";
auto const params = json::parse(R"({"queue": true})");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_TRUE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfAccountInfoQueueIsFalse)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "account_info";
auto const params = json::parse(R"({"queue": false})");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfLedgerWithQueueSpecified)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "ledger";
auto const params = json::parse(R"({"queue": true})");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_TRUE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfLedgerQueueIsFalse)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "ledger";
auto const params = json::parse(R"({"queue": false})");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldNotForwardReturnsTrueIfAPIVersionIsV1)
{
auto const apiVersion = 1u;
auto const method = "api_version_check";
auto const params = json::parse("{}");
auto const rawHandlerProviderPtr = handlerProvider.get();
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfAPIVersionIsV2)
{
auto const rawHandlerProviderPtr = handlerProvider.get();
auto const apiVersion = 2u;
auto const method = "api_version_check";
auto const params = json::parse("{}");
ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false));
EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1);
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldNeverForwardSubscribe)
{
auto const apiVersion = 1u;
auto const method = "subscribe";
auto const params = json::parse("{}");
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldNeverForwardUnsubscribe)
{
auto const apiVersion = 1u;
auto const method = "unsubscribe";
auto const params = json::parse("{}");
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
ASSERT_EQ(res, testBundle.expected);
});
}