mirror of
https://github.com/logos-blockchain/logos-blockchain-module.git
synced 2026-07-14 02:59:52 +00:00
Pipe new paths machinery
This commit is contained in:
parent
00d2b35715
commit
d3f4ac320d
@ -6,10 +6,12 @@
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Define static member
|
||||
@ -120,6 +122,8 @@ namespace {
|
||||
std::string http_addr_data;
|
||||
std::string external_address_data;
|
||||
std::string state_path_data;
|
||||
std::string storage_path_data;
|
||||
std::string logs_path_data;
|
||||
bool ibd_val;
|
||||
std::string log_filter_data;
|
||||
std::string kms_file_data;
|
||||
@ -195,6 +199,22 @@ namespace {
|
||||
ffi_args.state_path = nullptr;
|
||||
}
|
||||
|
||||
// storage_path (string -> const char*) — maps to storage.backend.folder_name
|
||||
if (args.contains("storage_path") && args["storage_path"].is_string()) {
|
||||
storage_path_data = args["storage_path"].get<std::string>();
|
||||
ffi_args.storage_path = storage_path_data.c_str();
|
||||
} else {
|
||||
ffi_args.storage_path = nullptr;
|
||||
}
|
||||
|
||||
// logs_path (string -> const char*) — maps to tracing.logger.file.directory
|
||||
if (args.contains("logs_path") && args["logs_path"].is_string()) {
|
||||
logs_path_data = args["logs_path"].get<std::string>();
|
||||
ffi_args.logs_path = logs_path_data.c_str();
|
||||
} else {
|
||||
ffi_args.logs_path = nullptr;
|
||||
}
|
||||
|
||||
// ibd (bool -> const bool*)
|
||||
if (args.contains("ibd") && args["ibd"].is_boolean()) {
|
||||
ibd_val = args["ibd"].get<bool>();
|
||||
@ -258,6 +278,75 @@ StdLogosResult LogosBlockchainModule::generate_user_config(const std::string& js
|
||||
return result::err(std::string("Failed to parse JSON args: ") + e.what());
|
||||
}
|
||||
|
||||
// The module-context getters are populated by every logos-core host
|
||||
// (logoscore-cli and Basecamp alike), so their mere presence can't tell the
|
||||
// two apart. The bundled app therefore opts in explicitly by passing
|
||||
// "use_persistence_paths": true; only then do we route the node's runtime
|
||||
// directories — state, storage (db) and logs — under the host-owned
|
||||
// per-instance persistence dir, so they all share one writable base. CLI and
|
||||
// standalone callers omit the flag and keep their own paths (or the node
|
||||
// defaults). Any path the caller set explicitly is left untouched.
|
||||
bool use_persistence_paths = false;
|
||||
if (const auto it = parsed_args.find("use_persistence_paths");
|
||||
it != parsed_args.end() && it->is_boolean()) {
|
||||
use_persistence_paths = it->get<bool>();
|
||||
}
|
||||
parsed_args.erase("use_persistence_paths"); // not an FFI field
|
||||
|
||||
if (use_persistence_paths) {
|
||||
const std::string& persistence = instancePersistencePath();
|
||||
if (!persistence.empty()) {
|
||||
const fs::path base(persistence);
|
||||
// Only fill a path the caller didn't pin (non-empty string wins).
|
||||
const auto set_if_absent = [&parsed_args](const char* key, const std::string& value) {
|
||||
const bool provided = parsed_args.contains(key)
|
||||
&& parsed_args[key].is_string()
|
||||
&& !parsed_args[key].get<std::string>().empty();
|
||||
if (!provided)
|
||||
parsed_args[key] = value;
|
||||
};
|
||||
set_if_absent("state_path", (base / "state").string());
|
||||
set_if_absent("storage_path", (base / "db").string());
|
||||
set_if_absent("logs_path", (base / "logs").string());
|
||||
|
||||
// The config file itself is written under the same base, using the
|
||||
// caller's path as the relative part below it ("config/user_config.yaml"
|
||||
// → "<base>/config/user_config.yaml"). Absolute / root-anchored inputs
|
||||
// (e.g. "//user_config.yaml" from QDir::currentPath()=="/") are treated
|
||||
// as relative to the base; a missing output defaults to
|
||||
// "<base>/user_config.yaml".
|
||||
fs::path output_rel = "user_config.yaml";
|
||||
if (parsed_args.contains("output") && parsed_args["output"].is_string()
|
||||
&& !parsed_args["output"].get<std::string>().empty()) {
|
||||
const fs::path given(localPathFromFileUrl(parsed_args["output"].get<std::string>()));
|
||||
const fs::path rel = given.relative_path();
|
||||
output_rel = rel.empty() ? given.filename() : rel;
|
||||
if (output_rel.empty())
|
||||
output_rel = "user_config.yaml";
|
||||
}
|
||||
parsed_args["output"] = (base / output_rel).lexically_normal().string();
|
||||
|
||||
fprintf(
|
||||
stderr,
|
||||
"generate_user_config: routing output/state/storage/logs under instance persistence path: %s\n",
|
||||
persistence.c_str()
|
||||
);
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
"generate_user_config: use_persistence_paths requested but no instance persistence path is set; leaving paths unchanged.\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The path the config is actually written to (after any persistence routing).
|
||||
// Returned to the caller so it can hand the exact path to start(). Empty only
|
||||
// when no output was given and no routing applied (the node wrote its own
|
||||
// default relative to the cwd, which the module can't resolve).
|
||||
std::string resolved_output;
|
||||
if (parsed_args.contains("output") && parsed_args["output"].is_string())
|
||||
resolved_output = parsed_args["output"].get<std::string>();
|
||||
|
||||
const OwnedGenerateConfigArgs owned_args(parsed_args);
|
||||
|
||||
const OperationStatus status = ::generate_user_config(owned_args.ffi_args);
|
||||
@ -266,7 +355,7 @@ StdLogosResult LogosBlockchainModule::generate_user_config(const std::string& js
|
||||
return result::err("Failed to generate user config.");
|
||||
}
|
||||
|
||||
return result::ok();
|
||||
return result::ok(resolved_output);
|
||||
}
|
||||
|
||||
StdLogosResult LogosBlockchainModule::start(const std::string& config_path, const std::string& deployment) {
|
||||
|
||||
@ -26,7 +26,15 @@ public:
|
||||
[[nodiscard]] StdLogosResult stop();
|
||||
|
||||
// Config management
|
||||
[[nodiscard]] static StdLogosResult generate_user_config(const std::string& json_args);
|
||||
// Not static: when the JSON args set "use_persistence_paths": true it routes
|
||||
// the node's output/state/storage/logs paths under instancePersistencePath()
|
||||
// (config at <base>/<relative output>; state/db/logs at <base>/state, /db,
|
||||
// /logs), so it needs the instance context. Basecamp opts in via that flag;
|
||||
// logoscore-cli/standalone omit it and keep their own paths. An explicit
|
||||
// state/storage/logs path always wins; output is always re-anchored under the
|
||||
// base when flagged. On success the result value is the path the config was
|
||||
// written to — pass it straight to start().
|
||||
[[nodiscard]] StdLogosResult generate_user_config(const std::string& json_args);
|
||||
[[nodiscard]] static StdLogosResult update_user_config(
|
||||
const std::string& user_config_path,
|
||||
const std::string& keystore_path
|
||||
|
||||
@ -6,6 +6,15 @@
|
||||
#include <logos_blockchain.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
// Captures the paths passed to the most recent generate_user_config call so tests
|
||||
// can assert the module's path routing. A null pointer is recorded as the
|
||||
// sentinel "<null>".
|
||||
std::string g_lastGeneratedOutput;
|
||||
std::string g_lastGeneratedStatePath;
|
||||
std::string g_lastGeneratedStoragePath;
|
||||
std::string g_lastGeneratedLogsPath;
|
||||
|
||||
static char s_fakeNode = 0;
|
||||
static CryptarchiaInfo s_fakeCryptarchiaInfo = {};
|
||||
@ -26,6 +35,10 @@ bool is_ok(const OperationStatus* status) {
|
||||
|
||||
OperationStatus generate_user_config(GenerateConfigArgs args) {
|
||||
LOGOS_CMOCK_RECORD("generate_user_config");
|
||||
g_lastGeneratedOutput = args.output ? args.output : "<null>";
|
||||
g_lastGeneratedStatePath = args.state_path ? args.state_path : "<null>";
|
||||
g_lastGeneratedStoragePath = args.storage_path ? args.storage_path : "<null>";
|
||||
g_lastGeneratedLogsPath = args.logs_path ? args.logs_path : "<null>";
|
||||
return LOGOS_CMOCK_RETURN(int, "generate_user_config");
|
||||
}
|
||||
|
||||
|
||||
@ -40,6 +40,8 @@ typedef struct {
|
||||
const char* http_addr;
|
||||
const char* external_address;
|
||||
const char* state_path;
|
||||
const char* storage_path;
|
||||
const char* logs_path;
|
||||
const bool* ibd;
|
||||
const char* log_filter;
|
||||
const char* kms_file;
|
||||
|
||||
@ -86,6 +86,122 @@ LOGOS_TEST(generate_user_config_from_json_string) {
|
||||
LOGOS_ASSERT(t.cFunctionCalled("generate_user_config"));
|
||||
}
|
||||
|
||||
// The mock records the paths handed to the FFI (see mock_logos_blockchain.cpp).
|
||||
extern std::string g_lastGeneratedOutput;
|
||||
extern std::string g_lastGeneratedStatePath;
|
||||
extern std::string g_lastGeneratedStoragePath;
|
||||
extern std::string g_lastGeneratedLogsPath;
|
||||
|
||||
static void clearGeneratedPaths() {
|
||||
g_lastGeneratedOutput.clear();
|
||||
g_lastGeneratedStatePath.clear();
|
||||
g_lastGeneratedStoragePath.clear();
|
||||
g_lastGeneratedLogsPath.clear();
|
||||
}
|
||||
|
||||
// Basecamp opts in with the flag: output and state/storage/logs are routed under
|
||||
// the per-instance persistence dir. The output keeps its given path as the
|
||||
// relative part below the base.
|
||||
LOGOS_TEST(generate_user_config_routes_paths_under_persistence_when_flagged) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
module._logosCoreSetContext_("/mod", "id", "/persist/data");
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
clearGeneratedPaths();
|
||||
|
||||
StdLogosResult result =
|
||||
module.generate_user_config(R"({"output":"config/user_config.yaml","use_persistence_paths":true})");
|
||||
LOGOS_ASSERT_TRUE(result.success);
|
||||
// The resolved config path is returned for the caller to hand to start().
|
||||
LOGOS_ASSERT_EQ(result.value.get<std::string>(), std::string("/persist/data/config/user_config.yaml"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedOutput, std::string("/persist/data/config/user_config.yaml"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStatePath, std::string("/persist/data/state"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStoragePath, std::string("/persist/data/db"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedLogsPath, std::string("/persist/data/logs"));
|
||||
}
|
||||
|
||||
// A root-anchored output (the UI's "//user_config.yaml" artifact) is treated as
|
||||
// relative to the base, not written to the read-only filesystem root.
|
||||
LOGOS_TEST(generate_user_config_routes_root_anchored_output_under_persistence) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
module._logosCoreSetContext_("/mod", "id", "/persist/data");
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
clearGeneratedPaths();
|
||||
|
||||
LOGOS_ASSERT_TRUE(
|
||||
module.generate_user_config(R"({"output":"//user_config.yaml","use_persistence_paths":true})").success);
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedOutput, std::string("/persist/data/user_config.yaml"));
|
||||
}
|
||||
|
||||
// With no output given, the config defaults to <base>/user_config.yaml.
|
||||
LOGOS_TEST(generate_user_config_defaults_output_under_persistence) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
module._logosCoreSetContext_("/mod", "id", "/persist/data");
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
clearGeneratedPaths();
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.generate_user_config(R"({"use_persistence_paths":true})").success);
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedOutput, std::string("/persist/data/user_config.yaml"));
|
||||
}
|
||||
|
||||
// A host load WITHOUT the flag (e.g. logoscore-cli) does NOT redirect any path,
|
||||
// even though the persistence path is populated.
|
||||
LOGOS_TEST(generate_user_config_host_without_flag_leaves_paths_unset) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
module._logosCoreSetContext_("/mod", "id", "/persist/data");
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
clearGeneratedPaths();
|
||||
|
||||
StdLogosResult result = module.generate_user_config(R"({"output":"/tmp/out.json"})");
|
||||
LOGOS_ASSERT_TRUE(result.success);
|
||||
// Without routing, the caller's output is returned unchanged.
|
||||
LOGOS_ASSERT_EQ(result.value.get<std::string>(), std::string("/tmp/out.json"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedOutput, std::string("/tmp/out.json"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStatePath, std::string("<null>"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStoragePath, std::string("<null>"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedLogsPath, std::string("<null>"));
|
||||
}
|
||||
|
||||
// An explicitly provided path wins over the flag; the others are still routed.
|
||||
LOGOS_TEST(generate_user_config_explicit_path_wins_over_flag) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
module._logosCoreSetContext_("/mod", "id", "/persist/data");
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
clearGeneratedPaths();
|
||||
|
||||
LOGOS_ASSERT_TRUE(
|
||||
module.generate_user_config(
|
||||
R"({"output":"/tmp/out.json","state_path":"/tmp/state","use_persistence_paths":true})").success);
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStatePath, std::string("/tmp/state"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStoragePath, std::string("/persist/data/db"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedLogsPath, std::string("/persist/data/logs"));
|
||||
}
|
||||
|
||||
// The flag with no host context (standalone) leaves paths unset.
|
||||
LOGOS_TEST(generate_user_config_flag_without_context_leaves_paths_unset) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
clearGeneratedPaths();
|
||||
|
||||
LOGOS_ASSERT_TRUE(
|
||||
module.generate_user_config(R"({"output":"/tmp/out.json","use_persistence_paths":true})").success);
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedOutput, std::string("/tmp/out.json"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStatePath, std::string("<null>"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedStoragePath, std::string("<null>"));
|
||||
LOGOS_ASSERT_EQ(g_lastGeneratedLogsPath, std::string("<null>"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(generate_user_config_with_all_fields) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user