mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-02 08:16:42 +00:00
feat(runtime-config): support startup message filters
This commit is contained in:
@@ -22,10 +22,37 @@
|
||||
#include <xrpld/overlay/detail/TrafficCount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class RuntimeConfig_test : public beast::unit_test::suite
|
||||
{
|
||||
class EnvVarGuard
|
||||
{
|
||||
public:
|
||||
EnvVarGuard(char const* name, char const* value) : name_(name)
|
||||
{
|
||||
if (auto const* old = std::getenv(name))
|
||||
old_ = old;
|
||||
setenv(name, value, 1);
|
||||
}
|
||||
|
||||
~EnvVarGuard()
|
||||
{
|
||||
if (old_)
|
||||
setenv(name_, old_->c_str(), 1);
|
||||
else
|
||||
unsetenv(name_);
|
||||
}
|
||||
|
||||
private:
|
||||
char const* name_;
|
||||
std::optional<std::string> old_;
|
||||
};
|
||||
|
||||
// Helper to call runtime_config RPC with JSON params
|
||||
Json::Value
|
||||
runtimeConfig(test::jtx::Env& env, Json::Value const& params)
|
||||
@@ -265,6 +292,71 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(!cfg->appliesTo(TrafficCount::category::base));
|
||||
}
|
||||
|
||||
void
|
||||
testMessageTypeAliases()
|
||||
{
|
||||
testcase("Message type aliases expand to multiple categories");
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
Json::Value params;
|
||||
params["set"] = Json::objectValue;
|
||||
params["set"]["*"] = Json::objectValue;
|
||||
params["set"]["*"]["send_delay_ms"] = 100;
|
||||
params["set"]["*"]["message_types"] = Json::arrayValue;
|
||||
params["set"]["*"]["message_types"].append("candidate_set_fetch");
|
||||
auto result = runtimeConfig(env, params);
|
||||
|
||||
auto const& global = result["configs"]["*"];
|
||||
BEAST_EXPECT(global.isMember("message_types"));
|
||||
BEAST_EXPECT(global["message_types"].size() == 4);
|
||||
|
||||
auto cfg = env.app().getRuntimeConfig().getConfig("*");
|
||||
if (!BEAST_EXPECT(cfg.has_value()))
|
||||
return;
|
||||
|
||||
BEAST_EXPECT(cfg->appliesTo(TrafficCount::category::gl_tsc_get));
|
||||
BEAST_EXPECT(cfg->appliesTo(TrafficCount::category::gl_tsc_share));
|
||||
BEAST_EXPECT(cfg->appliesTo(TrafficCount::category::ld_tsc_get));
|
||||
BEAST_EXPECT(cfg->appliesTo(TrafficCount::category::ld_tsc_share));
|
||||
BEAST_EXPECT(!cfg->appliesTo(TrafficCount::category::proposal));
|
||||
}
|
||||
|
||||
void
|
||||
testEnvMessageTypeFilter()
|
||||
{
|
||||
testcase("XAHAU_RUNTIME_CONFIG parses message_types");
|
||||
|
||||
EnvVarGuard guard{
|
||||
"XAHAU_RUNTIME_CONFIG",
|
||||
R"({"*":{"send_delay_ms":100,"message_types":["candidate_set_fetch"]}})"};
|
||||
|
||||
RuntimeConfig rc;
|
||||
auto cfg = rc.getConfig("*");
|
||||
if (!BEAST_EXPECT(cfg.has_value()))
|
||||
return;
|
||||
|
||||
BEAST_EXPECT(rc.active());
|
||||
BEAST_EXPECT(cfg->sendDelayMs == 100);
|
||||
BEAST_EXPECT(cfg->appliesTo(TrafficCount::category::gl_tsc_get));
|
||||
BEAST_EXPECT(cfg->appliesTo(TrafficCount::category::ld_tsc_share));
|
||||
BEAST_EXPECT(!cfg->appliesTo(TrafficCount::category::proposal));
|
||||
}
|
||||
|
||||
void
|
||||
testInvalidEnvMessageType()
|
||||
{
|
||||
testcase("Invalid XAHAU_RUNTIME_CONFIG message_types are ignored");
|
||||
|
||||
EnvVarGuard guard{
|
||||
"XAHAU_RUNTIME_CONFIG",
|
||||
R"({"*":{"send_delay_ms":100,"message_types":["not_a_category"]}})"};
|
||||
|
||||
RuntimeConfig rc;
|
||||
BEAST_EXPECT(!rc.active());
|
||||
BEAST_EXPECT(!rc.getConfig("*").has_value());
|
||||
}
|
||||
|
||||
void
|
||||
testMessageTypeFilterEmpty()
|
||||
{
|
||||
@@ -313,6 +405,25 @@ class RuntimeConfig_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(!env.app().getRuntimeConfig().active());
|
||||
}
|
||||
|
||||
void
|
||||
testInvalidMessageTypesShape()
|
||||
{
|
||||
testcase("Non-array message_types returns error");
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
Json::Value params;
|
||||
params["set"] = Json::objectValue;
|
||||
params["set"]["*"] = Json::objectValue;
|
||||
params["set"]["*"]["send_delay_ms"] = 100;
|
||||
params["set"]["*"]["message_types"] = "proposal";
|
||||
auto result = runtimeConfig(env, params);
|
||||
|
||||
BEAST_EXPECT(result.isMember("error"));
|
||||
BEAST_EXPECT(result["error"].asString() == "invalidParams");
|
||||
BEAST_EXPECT(!env.app().getRuntimeConfig().active());
|
||||
}
|
||||
|
||||
void
|
||||
testDropPctClamping()
|
||||
{
|
||||
@@ -515,8 +626,12 @@ public:
|
||||
testClearAll();
|
||||
testPerPeerWithoutGlobal();
|
||||
testMessageTypeFilter();
|
||||
testMessageTypeAliases();
|
||||
testEnvMessageTypeFilter();
|
||||
testInvalidEnvMessageType();
|
||||
testMessageTypeFilterEmpty();
|
||||
testInvalidMessageType();
|
||||
testInvalidMessageTypesShape();
|
||||
testDropPctClamping();
|
||||
testRngClaimDropPct();
|
||||
testRngClaimDropPctClamping();
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -177,6 +178,22 @@ private:
|
||||
std::unordered_map<std::string, ConfigVals> merged_;
|
||||
};
|
||||
|
||||
/** Expand runtime-config message type names into TrafficCount categories.
|
||||
|
||||
Names are intentionally string-based so test tools can target overlay
|
||||
traffic without depending on enum values. Aliases may expand to several
|
||||
categories, for example candidate-set fetch covers the TMGetLedger request
|
||||
and TMLedgerData reply categories used by tx-set and sidecar acquisition.
|
||||
*/
|
||||
std::optional<std::set<std::size_t>>
|
||||
runtimeConfigMessageCategoriesFromNames(
|
||||
std::vector<std::string> const& names,
|
||||
std::string& error);
|
||||
|
||||
/** Human-readable name for one runtime-config message category. */
|
||||
std::string
|
||||
runtimeConfigMessageCategoryName(std::size_t category);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/RuntimeConfig.h>
|
||||
#include <xrpld/overlay/detail/TrafficCount.h>
|
||||
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -25,12 +26,148 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace {
|
||||
using CategorySet = std::set<std::size_t>;
|
||||
|
||||
struct CategoryAlias
|
||||
{
|
||||
char const* name;
|
||||
std::vector<std::size_t> categories;
|
||||
};
|
||||
|
||||
std::vector<CategoryAlias> const&
|
||||
categoryAliases()
|
||||
{
|
||||
using C = TrafficCount::category;
|
||||
static std::vector<CategoryAlias> const aliases = {
|
||||
{"base", {C::base}},
|
||||
{"cluster", {C::cluster}},
|
||||
{"overlay", {C::overlay}},
|
||||
{"proposal", {C::proposal}},
|
||||
{"validation", {C::validation}},
|
||||
{"transaction", {C::transaction}},
|
||||
{"manifests", {C::manifests}},
|
||||
{"validator_list", {C::validatorlist}},
|
||||
{"validatorlist", {C::validatorlist}},
|
||||
|
||||
{"have_set", {C::get_set, C::share_set}},
|
||||
{"set_get", {C::get_set}},
|
||||
{"set_share", {C::share_set}},
|
||||
|
||||
{"candidate_set_fetch",
|
||||
{C::gl_tsc_get, C::gl_tsc_share, C::ld_tsc_get, C::ld_tsc_share}},
|
||||
{"candidate_set_request", {C::gl_tsc_get}},
|
||||
{"candidate_set_reply", {C::ld_tsc_share}},
|
||||
|
||||
{"ledger_data",
|
||||
{C::ld_tsc_get,
|
||||
C::ld_tsc_share,
|
||||
C::ld_txn_get,
|
||||
C::ld_txn_share,
|
||||
C::ld_asn_get,
|
||||
C::ld_asn_share,
|
||||
C::ld_get,
|
||||
C::ld_share}},
|
||||
{"ledger_data_tsc_get", {C::ld_tsc_get}},
|
||||
{"ledger_data_tsc_share", {C::ld_tsc_share}},
|
||||
{"ledger_data_txn_get", {C::ld_txn_get}},
|
||||
{"ledger_data_txn_share", {C::ld_txn_share}},
|
||||
{"ledger_data_asn_get", {C::ld_asn_get}},
|
||||
{"ledger_data_asn_share", {C::ld_asn_share}},
|
||||
{"ledger_data_get", {C::ld_get}},
|
||||
{"ledger_data_share", {C::ld_share}},
|
||||
|
||||
{"get_ledger",
|
||||
{C::gl_tsc_get,
|
||||
C::gl_tsc_share,
|
||||
C::gl_txn_get,
|
||||
C::gl_txn_share,
|
||||
C::gl_asn_get,
|
||||
C::gl_asn_share,
|
||||
C::gl_get,
|
||||
C::gl_share}},
|
||||
{"get_ledger_tsc_get", {C::gl_tsc_get}},
|
||||
{"get_ledger_tsc_share", {C::gl_tsc_share}},
|
||||
{"get_ledger_txn_get", {C::gl_txn_get}},
|
||||
{"get_ledger_txn_share", {C::gl_txn_share}},
|
||||
{"get_ledger_asn_get", {C::gl_asn_get}},
|
||||
{"get_ledger_asn_share", {C::gl_asn_share}},
|
||||
{"get_ledger_get", {C::gl_get}},
|
||||
{"get_ledger_share", {C::gl_share}},
|
||||
|
||||
{"get_object",
|
||||
{C::share_hash_ledger,
|
||||
C::get_hash_ledger,
|
||||
C::share_hash_tx,
|
||||
C::get_hash_tx,
|
||||
C::share_hash_txnode,
|
||||
C::get_hash_txnode,
|
||||
C::share_hash_asnode,
|
||||
C::get_hash_asnode,
|
||||
C::share_cas_object,
|
||||
C::get_cas_object,
|
||||
C::share_fetch_pack,
|
||||
C::get_fetch_pack,
|
||||
C::get_transactions,
|
||||
C::share_hash,
|
||||
C::get_hash}},
|
||||
{"get_object_fetch_pack", {C::share_fetch_pack, C::get_fetch_pack}},
|
||||
{"get_object_fetch_pack_get", {C::get_fetch_pack}},
|
||||
{"get_object_fetch_pack_share", {C::share_fetch_pack}},
|
||||
{"get_object_get", {C::get_hash}},
|
||||
{"get_object_share", {C::share_hash}},
|
||||
{"get_object_transactions", {C::get_transactions}},
|
||||
|
||||
{"proof_path", {C::proof_path_request, C::proof_path_response}},
|
||||
{"proof_path_request", {C::proof_path_request}},
|
||||
{"proof_path_response", {C::proof_path_response}},
|
||||
{"replay_delta", {C::replay_delta_request, C::replay_delta_response}},
|
||||
{"replay_delta_request", {C::replay_delta_request}},
|
||||
{"replay_delta_response", {C::replay_delta_response}},
|
||||
{"have_transactions", {C::have_transactions}},
|
||||
{"requested_transactions", {C::requested_transactions}},
|
||||
};
|
||||
return aliases;
|
||||
}
|
||||
|
||||
std::optional<CategorySet>
|
||||
categoriesForName(std::string const& name)
|
||||
{
|
||||
for (auto const& alias : categoryAliases())
|
||||
{
|
||||
if (name == alias.name)
|
||||
return CategorySet{
|
||||
alias.categories.begin(), alias.categories.end()};
|
||||
}
|
||||
|
||||
if (!name.empty() &&
|
||||
std::all_of(name.begin(), name.end(), [](unsigned char c) {
|
||||
return std::isdigit(c);
|
||||
}))
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const cat = static_cast<std::size_t>(std::stoull(name));
|
||||
if (cat <= TrafficCount::category::unknown)
|
||||
return CategorySet{cat};
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<bool>
|
||||
parseBoolEnv(char const* env)
|
||||
{
|
||||
@@ -51,7 +188,7 @@ parseBoolEnv(char const* env)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ConfigVals
|
||||
std::optional<ConfigVals>
|
||||
parseConfigVals(Json::Value const& v)
|
||||
{
|
||||
ConfigVals cfg;
|
||||
@@ -73,10 +210,56 @@ parseConfigVals(Json::Value const& v)
|
||||
cfg.rngPollMs = std::max(50, v["rng_poll_ms"].asInt());
|
||||
if (v.isMember("no_export_sig"))
|
||||
cfg.noExportSig = v["no_export_sig"].asBool();
|
||||
if (v.isMember("message_types"))
|
||||
{
|
||||
if (!v["message_types"].isArray())
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string> names;
|
||||
for (auto const& mt : v["message_types"])
|
||||
names.push_back(mt.asString());
|
||||
|
||||
std::string error;
|
||||
auto cats = runtimeConfigMessageCategoriesFromNames(names, error);
|
||||
if (!cats)
|
||||
return std::nullopt;
|
||||
cfg.messageCategories = *cats;
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<std::set<std::size_t>>
|
||||
runtimeConfigMessageCategoriesFromNames(
|
||||
std::vector<std::string> const& names,
|
||||
std::string& error)
|
||||
{
|
||||
CategorySet result;
|
||||
for (auto const& name : names)
|
||||
{
|
||||
auto cats = categoriesForName(name);
|
||||
if (!cats)
|
||||
{
|
||||
error = "Unknown message_type: " + name;
|
||||
return std::nullopt;
|
||||
}
|
||||
result.insert(cats->begin(), cats->end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
runtimeConfigMessageCategoryName(std::size_t category)
|
||||
{
|
||||
for (auto const& alias : categoryAliases())
|
||||
{
|
||||
if (alias.categories.size() == 1 &&
|
||||
alias.categories.front() == category)
|
||||
return alias.name;
|
||||
}
|
||||
return std::to_string(category);
|
||||
}
|
||||
|
||||
RuntimeConfig::RuntimeConfig()
|
||||
{
|
||||
// XAHAU_RUNTIME_CONFIG takes precedence (full JSON config)
|
||||
@@ -88,7 +271,10 @@ RuntimeConfig::RuntimeConfig()
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
for (auto const& target : root.getMemberNames())
|
||||
configs_[target] = parseConfigVals(root[target]);
|
||||
{
|
||||
if (auto cfg = parseConfigVals(root[target]))
|
||||
configs_[target] = *cfg;
|
||||
}
|
||||
rebuildMerged();
|
||||
updateActive();
|
||||
}
|
||||
|
||||
@@ -19,49 +19,12 @@
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/RuntimeConfig.h>
|
||||
#include <xrpld/overlay/detail/TrafficCount.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace {
|
||||
// Map user-friendly names to TrafficCount::category values.
|
||||
// Only the commonly useful categories are exposed; extend as needed.
|
||||
std::optional<std::size_t>
|
||||
categoryFromName(std::string const& name)
|
||||
{
|
||||
static std::unordered_map<std::string, std::size_t> const map = {
|
||||
{"proposal", TrafficCount::category::proposal},
|
||||
{"validation", TrafficCount::category::validation},
|
||||
{"transaction", TrafficCount::category::transaction},
|
||||
{"manifests", TrafficCount::category::manifests},
|
||||
{"ledger_data", TrafficCount::category::ld_share},
|
||||
{"get_ledger", TrafficCount::category::gl_get},
|
||||
};
|
||||
auto it = map.find(name);
|
||||
if (it != map.end())
|
||||
return it->second;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string
|
||||
categoryToName(std::size_t cat)
|
||||
{
|
||||
static std::unordered_map<std::size_t, std::string> const map = {
|
||||
{TrafficCount::category::proposal, "proposal"},
|
||||
{TrafficCount::category::validation, "validation"},
|
||||
{TrafficCount::category::transaction, "transaction"},
|
||||
{TrafficCount::category::manifests, "manifests"},
|
||||
{TrafficCount::category::ld_share, "ledger_data"},
|
||||
{TrafficCount::category::gl_get, "get_ledger"},
|
||||
};
|
||||
auto it = map.find(cat);
|
||||
if (it != map.end())
|
||||
return it->second;
|
||||
return std::to_string(cat);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Json::Value
|
||||
doRuntimeConfig(RPC::JsonContext& context)
|
||||
{
|
||||
@@ -111,24 +74,29 @@ doRuntimeConfig(RPC::JsonContext& context)
|
||||
if (v.isMember("message_types"))
|
||||
{
|
||||
auto const& mts = v["message_types"];
|
||||
cfg.messageCategories.emplace(); // set to empty = "all"
|
||||
if (mts.isArray())
|
||||
if (!mts.isArray())
|
||||
{
|
||||
for (auto const& mt : mts)
|
||||
{
|
||||
auto const name = mt.asString();
|
||||
auto cat = categoryFromName(name);
|
||||
if (!cat)
|
||||
{
|
||||
Json::Value err{Json::objectValue};
|
||||
err["error"] = "invalidParams";
|
||||
err["error_message"] =
|
||||
"Unknown message_type: " + name;
|
||||
return err;
|
||||
}
|
||||
cfg.messageCategories->insert(*cat);
|
||||
}
|
||||
Json::Value err{Json::objectValue};
|
||||
err["error"] = "invalidParams";
|
||||
err["error_message"] = "message_types must be an array";
|
||||
return err;
|
||||
}
|
||||
|
||||
std::vector<std::string> names;
|
||||
for (auto const& mt : mts)
|
||||
names.push_back(mt.asString());
|
||||
|
||||
std::string error;
|
||||
auto cats =
|
||||
runtimeConfigMessageCategoriesFromNames(names, error);
|
||||
if (!cats)
|
||||
{
|
||||
Json::Value err{Json::objectValue};
|
||||
err["error"] = "invalidParams";
|
||||
err["error_message"] = error;
|
||||
return err;
|
||||
}
|
||||
cfg.messageCategories = *cats;
|
||||
}
|
||||
rc.setConfig(target, cfg);
|
||||
}
|
||||
@@ -175,7 +143,7 @@ doRuntimeConfig(RPC::JsonContext& context)
|
||||
{
|
||||
Json::Value types{Json::arrayValue};
|
||||
for (auto cat : *cfg.messageCategories)
|
||||
types.append(categoryToName(cat));
|
||||
types.append(runtimeConfigMessageCategoryName(cat));
|
||||
entry["message_types"] = types;
|
||||
}
|
||||
configs[target] = entry;
|
||||
|
||||
Reference in New Issue
Block a user