Compare commits

..

9 Commits

Author SHA1 Message Date
Daniel
2946e0f853 Change version to 0.0.999 2026-07-13 11:27:25 +02:00
Daniel Sanchez
1b0b5a45b4
fix: Update join blend (#53)
* Update and simplify join blend method

* Update and simplify join blend method

* Update refs from latest ffi

* Fix commit version
2026-07-13 10:38:52 +02:00
Álex
88d57c92b5
chore(update): LB 0.2.0 (#50) 2026-06-29 18:55:58 +02:00
Daniel Sanchez
7415183bdc
chore: Devnet 0.2.0 rc.1 (#49)
* Update metadata.json and flake.lock

* Bring back old trusty builder
2026-06-26 23:37:40 +02:00
Álex
aa87e35464
chore(dependencies): Update to use LB better errors (#47)
* Adapt module to OperationStatus result-like update.

* Prettify.

* Update flake.
2026-06-26 22:59:13 +02:00
Khushboo-dev-cpp
6aef6ee230
chore: bump logos-module-builder (#48) 2026-06-26 22:21:46 +02:00
Daniel Sanchez
9af5e9b77b
Update module name and version to release 0.1.3-rc.13 (#46) 2026-06-26 18:17:45 +02:00
Daniel Sanchez
b9d7117712
fix: Fix paths for logos core embedded in basecamp (#45)
* Remove circuits directory setup

* Pipe new paths machinery

* Update logos-blockchain tip
2026-06-25 09:04:34 +02:00
gusto
a5098cfb12
aarch64-macos suffix for module (#44) 2026-06-24 16:50:00 +03:00
12 changed files with 412 additions and 303 deletions

View File

@ -16,7 +16,7 @@ jobs:
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
arch: aarch64-linux arch: aarch64-linux
- os: macos-14 - os: macos-14
arch: aarch64-darwin arch: aarch64-macos
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:

View File

@ -9,7 +9,7 @@ endif()
# Universal module generated_code/ is picked up automatically by LogosModule.cmake # Universal module generated_code/ is picked up automatically by LogosModule.cmake
logos_module( logos_module(
NAME liblogos_blockchain_module NAME blockchain_module
SOURCES SOURCES
src/logos_blockchain_module.h src/logos_blockchain_module.h
src/logos_blockchain_module.cpp src/logos_blockchain_module.cpp

View File

@ -1,6 +1,6 @@
# Logos Blockchain Module # Logos Blockchain Module
A Logos core module that wraps the [logos-blockchain](https://github.com/logos-blockchain/logos-blockchain) C bindings and ships the zk circuit binaries needed at runtime. A Logos core module that wraps the [logos-blockchain](https://github.com/logos-blockchain/logos-blockchain) C bindings.
### Build and inspect ### Build and inspect

8
flake.lock generated
View File

@ -24,16 +24,16 @@
"rust-rapidsnark": "rust-rapidsnark" "rust-rapidsnark": "rust-rapidsnark"
}, },
"locked": { "locked": {
"lastModified": 1782212034, "lastModified": 1783712095,
"narHash": "sha256-IcU+c3YokqSmv4huTHZsBUhsen8lLs3wGvtaMWfQths=", "narHash": "sha256-N5uloPS1FGvG/ClJmga8/K8w+2hxQBJIRP68ide4H58=",
"owner": "logos-blockchain", "owner": "logos-blockchain",
"repo": "logos-blockchain", "repo": "logos-blockchain",
"rev": "113faae6fd5f68aa70e3cece4fa54f8522c95eec", "rev": "ec648fa7239576dccb9aa8b684d4a780c9064797",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "logos-blockchain", "owner": "logos-blockchain",
"ref": "113faae6fd5f68aa70e3cece4fa54f8522c95eec", "ref": "ec648fa7239576dccb9aa8b684d4a780c9064797",
"repo": "logos-blockchain", "repo": "logos-blockchain",
"type": "github" "type": "github"
} }

View File

@ -3,8 +3,7 @@
inputs = { inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder?ref=38ddf92c1f240f4e420d300a1fbabb1609d5db01"; logos-module-builder.url = "github:logos-co/logos-module-builder?ref=38ddf92c1f240f4e420d300a1fbabb1609d5db01";
# https://github.com/logos-blockchain/logos-blockchain/commit/113faae6fd5f68aa70e3cece4fa54f8522c95eec logos-blockchain.url = "github:logos-blockchain/logos-blockchain?ref=ec648fa7239576dccb9aa8b684d4a780c9064797";
logos-blockchain.url = "github:logos-blockchain/logos-blockchain?ref=113faae6fd5f68aa70e3cece4fa54f8522c95eec";
}; };
outputs = inputs@{ logos-module-builder, ... }: outputs = inputs@{ logos-module-builder, ... }:
@ -22,35 +21,7 @@
mockCLibs = [ "logos_blockchain" ]; mockCLibs = [ "logos_blockchain" ];
}; };
preConfigure = { externalLibs }:
if externalLibs ? logos_blockchain then ''
if [ -d "${externalLibs.logos_blockchain}/circuits" ]; then
echo "Staging zk circuits from logos-blockchain..."
cp -r "${externalLibs.logos_blockchain}/circuits" ./circuits
chmod -R u+w ./circuits
else
echo "WARNING: no circuits/ found in logos-blockchain derivation"
fi
'' else ''
echo "Skipping zk circuits staging (logos_blockchain mocked for tests)"
'';
# Logos Core Edge-case
# The current version of Logos Core expects circuits' binaries under `lib/circuits/`.
# Until we address this in Logos Core, we use this hook to include to ensure the circuits' binaries
# are included in the binary bundle and avoid the circuits being mangled by Nix (which did that when
# copying them in a previous phase).
postInstall = '' postInstall = ''
if [ -d "$LOGOS_MODULE_SOURCE_DIR/circuits" ]; then
cp -r "$LOGOS_MODULE_SOURCE_DIR/circuits" "$out/lib/circuits"
chmod -R u+w "$out/lib/circuits"
if [ -f "$out/lib/circuits/lib/libgmp.a" ]; then
echo "Removing loose static library libgmp.a from staged circuits..."
rm "$out/lib/circuits/lib/libgmp.a"
fi
fi
# Remove nix references to make the module portable. # Remove nix references to make the module portable.
find "$out" -type f | while read -r binary; do find "$out" -type f | while read -r binary; do
if file "$binary" | grep -E -q "Mach-O|shared library|executable|archive"; then if file "$binary" | grep -E -q "Mach-O|shared library|executable|archive"; then

View File

@ -7,7 +7,7 @@ configure:
cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
build: configure build: configure
cmake --build build --parallel --target liblogos_blockchain_module_module_plugin cmake --build build --parallel --target blockchain_module_module_plugin
clean: clean:
rm -rf build result rm -rf build result

View File

@ -1,7 +1,7 @@
{ {
"name": "liblogos_blockchain_module", "name": "blockchain_module",
"display_name": "Blockchain Module", "display_name": "Blockchain Module",
"version": "1.0.0", "version": "0.0.999",
"description": "Logos blockchain node for logos-core", "description": "Logos blockchain node for logos-core",
"author": "Logos Blockchain Team", "author": "Logos Blockchain Team",
"type": "core", "type": "core",
@ -11,17 +11,16 @@
"impl_class": "LogosBlockchainModule" "impl_class": "LogosBlockchainModule"
}, },
"category": "blockchain", "category": "blockchain",
"main": "liblogos_blockchain_module_plugin", "main": "blockchain_module_plugin",
"dependencies": [], "dependencies": [],
"capabilities": [], "capabilities": [],
"include": [ "include": [
"liblogos_blockchain.dylib", "liblogos_blockchain.dylib",
"liblogos_blockchain.so", "liblogos_blockchain.so",
"liblogos_blockchain.dll", "liblogos_blockchain.dll",
"liblogos_blockchain_module_plugin.dylib", "blockchain_module_plugin.dylib",
"liblogos_blockchain_module_plugin.so", "blockchain_module_plugin.so",
"liblogos_blockchain_module_plugin.dll", "blockchain_module_plugin.dll"
"circuits"
], ],
"nix": { "nix": {

View File

@ -17,6 +17,19 @@ using json = nlohmann::json;
// Define static member // Define static member
LogosBlockchainModule* LogosBlockchainModule::s_instance = nullptr; LogosBlockchainModule* LogosBlockchainModule::s_instance = nullptr;
namespace operation_status {
// Takes the Rust-allocated message out of an OperationStatus and frees it.
std::string take_message(OperationStatus& status) {
std::string message;
if (status.message) {
message = status.message;
(void)free_cstring(status.message);
status.message = nullptr;
}
return message;
}
} // namespace operation_status
// Shorthands for building StdLogosResult values. // Shorthands for building StdLogosResult values.
namespace result { namespace result {
StdLogosResult ok() { StdLogosResult ok() {
@ -31,6 +44,13 @@ namespace result {
StdLogosResult err(std::string message) { StdLogosResult err(std::string message) {
return {false, {}, std::move(message)}; return {false, {}, std::move(message)};
} }
StdLogosResult from_operation_status(OperationStatus& status) {
if (is_ok(&status)) {
return ok();
}
return err(operation_status::take_message(status));
}
} // namespace result } // namespace result
namespace { namespace {
@ -122,7 +142,9 @@ namespace {
std::string http_addr_data; std::string http_addr_data;
std::string external_address_data; std::string external_address_data;
std::string state_path_data; std::string state_path_data;
bool ibd_val; std::string storage_path_data;
std::string logs_path_data;
bool skip_ibd_val;
std::string log_filter_data; std::string log_filter_data;
std::string kms_file_data; std::string kms_file_data;
@ -197,12 +219,28 @@ namespace {
ffi_args.state_path = nullptr; ffi_args.state_path = nullptr;
} }
// ibd (bool -> const bool*) // storage_path (string -> const char*) — maps to storage.backend.folder_name
if (args.contains("ibd") && args["ibd"].is_boolean()) { if (args.contains("storage_path") && args["storage_path"].is_string()) {
ibd_val = args["ibd"].get<bool>(); storage_path_data = args["storage_path"].get<std::string>();
ffi_args.ibd = &ibd_val; ffi_args.storage_path = storage_path_data.c_str();
} else { } else {
ffi_args.ibd = nullptr; 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;
}
// skip_ibd (bool -> const bool*)
if (args.contains("skip_ibd") && args["skip_ibd"].is_boolean()) {
skip_ibd_val = args["skip_ibd"].get<bool>();
ffi_args.skip_ibd = &skip_ibd_val;
} else {
ffi_args.skip_ibd = nullptr;
} }
// log_filter (string -> const char*) // log_filter (string -> const char*)
@ -224,37 +262,6 @@ namespace {
}; };
} // namespace } // namespace
namespace environment {
constexpr auto LOGOS_BLOCKCHAIN_CIRCUITS = "LOGOS_BLOCKCHAIN_CIRCUITS";
bool is_circuits_path_valid(const std::string& path) {
std::error_code ec;
if (!fs::is_directory(path, ec))
return false;
for (const auto& entry : fs::directory_iterator(path, ec)) { // NOLINT(*-use-anyofallof)
if (entry.is_regular_file())
return true;
}
return false;
}
void setup_circuits_path(const std::string& module_path) {
const fs::path circuits_path = fs::path(module_path) / "circuits";
const std::string circuits_str = circuits_path.string();
if (!is_circuits_path_valid(circuits_str)) {
fprintf(
stderr,
"FATAL: The LOGOS_BLOCKCHAIN_CIRCUITS environment variable is not set or does not contain any files.\n"
);
return;
}
setenv("LOGOS_BLOCKCHAIN_CIRCUITS", circuits_str.c_str(), 1);
fprintf(stderr, "LOGOS_BLOCKCHAIN_CIRCUITS set to: %s\n", circuits_str.c_str());
}
} // namespace environment
void LogosBlockchainModule::on_new_block_callback(const char* block) { void LogosBlockchainModule::on_new_block_callback(const char* block) {
if (s_instance) { if (s_instance) {
fprintf(stderr, "Received new block: %s\n", block); fprintf(stderr, "Received new block: %s\n", block);
@ -282,7 +289,7 @@ LogosBlockchainModule::~LogosBlockchainModule() {
// Lifecycle // Lifecycle
StdLogosResult LogosBlockchainModule::generate_user_config(const std::string& json_args) { StdLogosResult LogosBlockchainModule::generate_user_config(const std::string& json_args) const {
json parsed_args; json parsed_args;
try { try {
parsed_args = json::parse(json_args); parsed_args = json::parse(json_args);
@ -291,15 +298,82 @@ StdLogosResult LogosBlockchainModule::generate_user_config(const std::string& js
return result::err(std::string("Failed to parse JSON args: ") + e.what()); return result::err(std::string("Failed to parse JSON args: ") + e.what());
} }
const OwnedGenerateConfigArgs owned_args(parsed_args); // 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
const OperationStatus status = ::generate_user_config(owned_args.ffi_args); if (use_persistence_paths) {
if (!is_ok(&status)) { const std::string& persistence = instancePersistencePath();
fprintf(stderr, "Failed to generate user config. Error: %d\n", status); if (!persistence.empty()) {
return result::err("Failed to generate user config."); 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"
);
}
} }
return result::ok(); // 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);
OperationStatus status = ::generate_user_config(owned_args.ffi_args);
if (!is_ok(&status)) {
return result::err(operation_status::take_message(status));
}
return result::ok(resolved_output);
} }
StdLogosResult LogosBlockchainModule::start(const std::string& config_path, const std::string& deployment) { StdLogosResult LogosBlockchainModule::start(const std::string& config_path, const std::string& deployment) {
@ -308,13 +382,6 @@ StdLogosResult LogosBlockchainModule::start(const std::string& config_path, cons
return result::err("The node is already running."); return result::err("The node is already running.");
} }
const char* module_path_env = std::getenv("LOGOS_MODULE_PATH");
if (module_path_env && *module_path_env) {
environment::setup_circuits_path(module_path_env);
} else {
fprintf(stderr, "Warning: LOGOS_MODULE_PATH not set, skipping circuits' path setup.\n");
}
std::string effective_config_path = config_path; std::string effective_config_path = config_path;
if (effective_config_path.empty()) { if (effective_config_path.empty()) {
@ -335,28 +402,19 @@ StdLogosResult LogosBlockchainModule::start(const std::string& config_path, cons
const char* deployment_ptr = deployment_path.empty() ? nullptr : deployment_path.c_str(); const char* deployment_ptr = deployment_path.empty() ? nullptr : deployment_path.c_str();
auto [value, error] = start_lb_node(config_path_ptr, deployment_ptr); auto [value, error] = start_lb_node(config_path_ptr, deployment_ptr);
fprintf(stderr, "Start node returned with value and error.\n");
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to start the node. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to start the node.");
} }
node = value; node = value;
fprintf(stderr, "The node was started successfully.\n");
if (!node) { if (!node) {
fprintf(stderr, "Could not subscribe to block events: The node is not running.\n");
return result::err("Could not subscribe to block events: the node is not running."); return result::err("Could not subscribe to block events: the node is not running.");
} }
s_instance = this; s_instance = this;
const OperationStatus subscribe_status = subscribe_to_new_blocks(node, on_new_block_callback); OperationStatus subscribe_status = subscribe_to_new_blocks(node, on_new_block_callback);
if (!is_ok(&subscribe_status)) { return result::from_operation_status(subscribe_status);
fprintf(stderr, "Failed to subscribe to new blocks. Error: %d\n", subscribe_status);
return result::err("Failed to subscribe to new blocks.");
}
return result::ok();
} }
StdLogosResult LogosBlockchainModule::stop() { StdLogosResult LogosBlockchainModule::stop() {
@ -367,11 +425,9 @@ StdLogosResult LogosBlockchainModule::stop() {
s_instance = nullptr; s_instance = nullptr;
const OperationStatus status = stop_node(node); OperationStatus status = shutdown_node(node);
if (is_ok(&status)) { if (!is_ok(&status)) {
fprintf(stderr, "The node was stopped successfully.\n"); fprintf(stderr, "Could not stop the node: %s\n", operation_status::take_message(status).c_str());
} else {
fprintf(stderr, "Could not stop the node. Error: %d\n", status);
} }
node = nullptr; node = nullptr;
@ -387,12 +443,8 @@ StdLogosResult LogosBlockchainModule::update_user_config(
const std::string config = localPathFromFileUrl(user_config_path); const std::string config = localPathFromFileUrl(user_config_path);
const std::string keystore = localPathFromFileUrl(keystore_path); const std::string keystore = localPathFromFileUrl(keystore_path);
const OperationStatus status = ::update_user_config(config.c_str(), keystore.c_str()); OperationStatus status = ::update_user_config(config.c_str(), keystore.c_str());
if (!is_ok(&status)) { return result::from_operation_status(status);
fprintf(stderr, "Failed to update user config. Error: %d\n", status);
return result::err("Failed to update user config.");
}
return result::ok();
} }
StdLogosResult LogosBlockchainModule::migrate_user_config( StdLogosResult LogosBlockchainModule::migrate_user_config(
@ -402,12 +454,8 @@ StdLogosResult LogosBlockchainModule::migrate_user_config(
const std::string output = localPathFromFileUrl(output_path); const std::string output = localPathFromFileUrl(output_path);
const std::string keystore = localPathFromFileUrl(keystore_path); const std::string keystore = localPathFromFileUrl(keystore_path);
const OperationStatus status = ::migrate_user_config(output.c_str(), keystore.c_str()); OperationStatus status = ::migrate_user_config(output.c_str(), keystore.c_str());
if (!is_ok(&status)) { return result::from_operation_status(status);
fprintf(stderr, "Failed to migrate user config. Error: %d\n", status);
return result::err("Failed to migrate user config.");
}
return result::ok();
} }
StdLogosResult LogosBlockchainModule::migrate_user_config_0_1_2( StdLogosResult LogosBlockchainModule::migrate_user_config_0_1_2(
@ -419,13 +467,8 @@ StdLogosResult LogosBlockchainModule::migrate_user_config_0_1_2(
const std::string old_config = localPathFromFileUrl(old_config_path); const std::string old_config = localPathFromFileUrl(old_config_path);
const std::string keystore = localPathFromFileUrl(keystore_path); const std::string keystore = localPathFromFileUrl(keystore_path);
const OperationStatus status = OperationStatus status = ::migrate_user_config_0_1_2(new_config.c_str(), old_config.c_str(), keystore.c_str());
::migrate_user_config_0_1_2(new_config.c_str(), old_config.c_str(), keystore.c_str()); return result::from_operation_status(status);
if (!is_ok(&status)) {
fprintf(stderr, "Failed to migrate 0.1.2 config. Error: %d\n", status);
return result::err("Failed to migrate 0.1.2 config.");
}
return result::ok();
} }
StdLogosResult LogosBlockchainModule::participate( StdLogosResult LogosBlockchainModule::participate(
@ -439,13 +482,8 @@ StdLogosResult LogosBlockchainModule::participate(
const std::string output = localPathFromFileUrl(output_dir); const std::string output = localPathFromFileUrl(output_dir);
const char* external_address_ptr = external_address.empty() ? nullptr : external_address.c_str(); const char* external_address_ptr = external_address.empty() ? nullptr : external_address.c_str();
const OperationStatus status = OperationStatus status = ::participate(config.c_str(), keystore.c_str(), output.c_str(), external_address_ptr);
::participate(config.c_str(), keystore.c_str(), output.c_str(), external_address_ptr); return result::from_operation_status(status);
if (!is_ok(&status)) {
fprintf(stderr, "Failed to generate participation data. Error: %d\n", status);
return result::err("Failed to generate participation data.");
}
return result::ok();
} }
// Keystore // Keystore
@ -467,14 +505,13 @@ StdLogosResult LogosBlockchainModule::generate_key(
auto [value, error] = ::generate_key(config.c_str(), keystore.c_str(), type, key_title_ptr); auto [value, error] = ::generate_key(config.c_str(), keystore.c_str(), type, key_title_ptr);
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to generate key. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to generate key: " + std::to_string(error));
} }
const std::string out(value); const std::string out(value);
const OperationStatus free_status = free_cstring(value); OperationStatus free_status = free_cstring(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free key id string. Error: %d\n", free_status); fprintf(stderr, "Failed to free key id string: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(out); return result::ok(out);
} }
@ -496,12 +533,8 @@ StdLogosResult LogosBlockchainModule::add_key(
const std::string keystore = localPathFromFileUrl(keystore_path); const std::string keystore = localPathFromFileUrl(keystore_path);
const char* key_title_ptr = key_title.empty() ? nullptr : key_title.c_str(); const char* key_title_ptr = key_title.empty() ? nullptr : key_title.c_str();
const OperationStatus status = ::add_key(config.c_str(), keystore.c_str(), type, key_hex.c_str(), key_title_ptr); OperationStatus status = ::add_key(config.c_str(), keystore.c_str(), type, key_hex.c_str(), key_title_ptr);
if (!is_ok(&status)) { return result::from_operation_status(status);
fprintf(stderr, "Failed to add key. Error: %d\n", status);
return result::err("Failed to add key.");
}
return result::ok();
} }
StdLogosResult LogosBlockchainModule::remove_key( StdLogosResult LogosBlockchainModule::remove_key(
@ -512,12 +545,8 @@ StdLogosResult LogosBlockchainModule::remove_key(
const std::string config = localPathFromFileUrl(user_config_path); const std::string config = localPathFromFileUrl(user_config_path);
const std::string keystore = localPathFromFileUrl(keystore_path); const std::string keystore = localPathFromFileUrl(keystore_path);
const OperationStatus status = ::remove_key(config.c_str(), keystore.c_str(), key_title.c_str()); OperationStatus status = ::remove_key(config.c_str(), keystore.c_str(), key_title.c_str());
if (!is_ok(&status)) { return result::from_operation_status(status);
fprintf(stderr, "Failed to remove key. Error: %d\n", status);
return result::err("Failed to remove key.");
}
return result::ok();
} }
// Identity // Identity
@ -527,14 +556,13 @@ StdLogosResult LogosBlockchainModule::get_peer_id(const std::string& config_path
auto [value, error] = ::get_peer_id(config.c_str()); auto [value, error] = ::get_peer_id(config.c_str());
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to get peer id. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to get peer id: " + std::to_string(error));
} }
const std::string out(value); const std::string out(value);
const OperationStatus free_status = free_cstring(value); OperationStatus free_status = free_cstring(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free peer id string. Error: %d\n", free_status); fprintf(stderr, "Failed to free peer id string: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(out); return result::ok(out);
} }
@ -554,7 +582,7 @@ StdLogosResult LogosBlockchainModule::wallet_get_balance(const std::string& addr
auto [value, error] = get_balance(node, bytes.data(), nullptr); auto [value, error] = get_balance(node, bytes.data(), nullptr);
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to get balance: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
return result::ok(std::to_string(value)); return result::ok(std::to_string(value));
@ -622,7 +650,7 @@ StdLogosResult LogosBlockchainModule::wallet_transfer_funds(
auto [value, error] = transfer_funds(node, &args); auto [value, error] = transfer_funds(node, &args);
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to transfer funds: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES)); return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES));
} }
@ -634,8 +662,7 @@ StdLogosResult LogosBlockchainModule::wallet_get_known_addresses() const {
} }
auto [value, error] = get_known_addresses(node); auto [value, error] = get_known_addresses(node);
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to get known addresses. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to get known addresses: " + std::to_string(error));
} }
std::vector<std::string> out; std::vector<std::string> out;
for (size_t i = 0; i < value.len; ++i) { for (size_t i = 0; i < value.len; ++i) {
@ -645,9 +672,9 @@ StdLogosResult LogosBlockchainModule::wallet_get_known_addresses() const {
out.push_back(bytes_to_hex(ptr, ADDRESS_BYTES)); out.push_back(bytes_to_hex(ptr, ADDRESS_BYTES));
} }
} }
const OperationStatus free_status = free_known_addresses(value); OperationStatus free_status = free_known_addresses(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free known addresses. Error: %d\n", free_status); fprintf(stderr, "Failed to free known addresses: %s\n", operation_status::take_message(free_status).c_str());
} }
fprintf( fprintf(
stderr, stderr,
@ -683,7 +710,7 @@ StdLogosResult LogosBlockchainModule::wallet_get_notes(
auto [value, error] = get_wallet_notes(node, address_bytes.data(), optional_tip); auto [value, error] = get_wallet_notes(node, address_bytes.data(), optional_tip);
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to get wallet notes: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
json obj; json obj;
@ -699,9 +726,9 @@ StdLogosResult LogosBlockchainModule::wallet_get_notes(
} }
obj["notes"] = std::move(notes); obj["notes"] = std::move(notes);
const OperationStatus free_status = free_wallet_notes(value); OperationStatus free_status = free_wallet_notes(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free wallet notes. Error: %d\n", free_status); fprintf(stderr, "Failed to free wallet notes: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(obj.dump()); return result::ok(obj.dump());
} }
@ -713,7 +740,7 @@ StdLogosResult LogosBlockchainModule::leader_claim() const {
auto [value, error] = ::leader_claim(node); auto [value, error] = ::leader_claim(node);
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to claim leader rewards: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), TX_HASH_BYTES)); return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), TX_HASH_BYTES));
@ -778,7 +805,7 @@ StdLogosResult LogosBlockchainModule::channel_deposit(
auto [value, error] = ::channel_deposit(node, &args); auto [value, error] = ::channel_deposit(node, &args);
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to deposit into channel: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES)); return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES));
} }
@ -874,7 +901,7 @@ StdLogosResult LogosBlockchainModule::channel_deposit_with_notes(
auto [value, error] = ::channel_deposit_with_notes(node, &args); auto [value, error] = ::channel_deposit_with_notes(node, &args);
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to deposit into channel: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES)); return result::ok(bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES));
} }
@ -886,7 +913,7 @@ StdLogosResult LogosBlockchainModule::wallet_get_claimable_vouchers() const {
auto [value, error] = get_claimable_vouchers(node, nullptr); auto [value, error] = get_claimable_vouchers(node, nullptr);
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to get claimable vouchers: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
json obj; json obj;
@ -901,9 +928,9 @@ StdLogosResult LogosBlockchainModule::wallet_get_claimable_vouchers() const {
}); });
} }
const OperationStatus free_status = free_claimable_vouchers(value); OperationStatus free_status = free_claimable_vouchers(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free claimable vouchers. Error: %d\n", free_status); fprintf(stderr, "Failed to free claimable vouchers: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(obj.dump()); return result::ok(obj.dump());
@ -912,23 +939,15 @@ StdLogosResult LogosBlockchainModule::wallet_get_claimable_vouchers() const {
// Blend // Blend
StdLogosResult LogosBlockchainModule::blend_join_as_core_node( StdLogosResult LogosBlockchainModule::blend_join_as_core_node(
const std::string& provider_id_hex, const std::string& locator,
const std::string& zk_id_hex, const std::string& locked_note_id_hex
const std::string& locked_note_id_hex,
const std::vector<std::string>& locators
) const { ) const {
if (!node) { if (!node) {
return result::err("The node is not running."); return result::err("The node is not running.");
} }
const std::vector<uint8_t> provider_id_bytes = parse_address_hex(provider_id_hex); if (locator.empty()) {
if (provider_id_bytes.empty() || static_cast<int>(provider_id_bytes.size()) != ADDRESS_BYTES) { return result::err("Invalid locator (must not be empty).");
return result::err("Invalid provider_id_hex (64 hex characters required).");
}
const std::vector<uint8_t> zk_id_bytes = parse_address_hex(zk_id_hex);
if (zk_id_bytes.empty() || static_cast<int>(zk_id_bytes.size()) != ADDRESS_BYTES) {
return result::err("Invalid zk_id_hex (64 hex characters required).");
} }
const std::vector<uint8_t> locked_note_id_bytes = parse_address_hex(locked_note_id_hex); const std::vector<uint8_t> locked_note_id_bytes = parse_address_hex(locked_note_id_hex);
@ -936,23 +955,13 @@ StdLogosResult LogosBlockchainModule::blend_join_as_core_node(
return result::err("Invalid locked_note_id_hex (64 hex characters required)."); return result::err("Invalid locked_note_id_hex (64 hex characters required).");
} }
// locators_ptrs holds raw pointers into the std::strings (valid as long as `locators` lives).
std::vector<const char*> locators_ptrs;
locators_ptrs.reserve(locators.size());
for (const std::string& locator : locators) {
locators_ptrs.push_back(locator.c_str());
}
auto [value, error] = ::blend_join_as_core_node( auto [value, error] = ::blend_join_as_core_node(
node, node,
provider_id_bytes.data(), locator.c_str(),
zk_id_bytes.data(), locked_note_id_bytes.data()
locked_note_id_bytes.data(),
locators_ptrs.data(),
locators_ptrs.size()
); );
if (!is_ok(&error)) { if (!is_ok(&error)) {
return result::err("Failed to join as core node: " + std::to_string(error)); return result::err(operation_status::take_message(error));
} }
std::string declaration_id = bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), sizeof(value)); std::string declaration_id = bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), sizeof(value));
@ -974,14 +983,13 @@ StdLogosResult LogosBlockchainModule::get_block(const std::string& header_id_hex
auto [value, error] = ::get_block(node, reinterpret_cast<const HeaderId*>(bytes.data())); auto [value, error] = ::get_block(node, reinterpret_cast<const HeaderId*>(bytes.data()));
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to get block. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to get block: " + std::to_string(error));
} }
std::string out(value); std::string out(value);
const OperationStatus free_status = free_cstring(value); OperationStatus free_status = free_cstring(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free block string. Error: %d\n", free_status); fprintf(stderr, "Failed to free block string: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(std::move(out)); return result::ok(std::move(out));
} }
@ -993,14 +1001,13 @@ StdLogosResult LogosBlockchainModule::get_blocks(const uint64_t from_slot, const
auto [value, error] = ::get_blocks(node, from_slot, to_slot); auto [value, error] = ::get_blocks(node, from_slot, to_slot);
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to get blocks. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to get blocks: " + std::to_string(error));
} }
std::string out(value); std::string out(value);
const OperationStatus free_status = free_cstring(value); OperationStatus free_status = free_cstring(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free blocks string. Error: %d\n", free_status); fprintf(stderr, "Failed to free blocks string: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(std::move(out)); return result::ok(std::move(out));
} }
@ -1017,14 +1024,13 @@ StdLogosResult LogosBlockchainModule::get_transaction(const std::string& tx_hash
auto [value, error] = ::get_transaction(node, reinterpret_cast<const TxHash*>(bytes.data())); auto [value, error] = ::get_transaction(node, reinterpret_cast<const TxHash*>(bytes.data()));
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to get transaction. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to get transaction: " + std::to_string(error));
} }
std::string out(value); std::string out(value);
const OperationStatus free_status = free_cstring(value); OperationStatus free_status = free_cstring(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free transaction string. Error: %d\n", free_status); fprintf(stderr, "Failed to free transaction string: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(std::move(out)); return result::ok(std::move(out));
} }
@ -1038,8 +1044,7 @@ StdLogosResult LogosBlockchainModule::get_cryptarchia_info() const {
auto [value, error] = ::get_cryptarchia_info(node); auto [value, error] = ::get_cryptarchia_info(node);
if (!is_ok(&error)) { if (!is_ok(&error)) {
fprintf(stderr, "Failed to get cryptarchia info. Error: %d\n", error); return result::err(operation_status::take_message(error));
return result::err("Failed to get cryptarchia info: " + std::to_string(error));
} }
json obj; json obj;
@ -1049,9 +1054,9 @@ StdLogosResult LogosBlockchainModule::get_cryptarchia_info() const {
obj["height"] = static_cast<int64_t>(value->height); obj["height"] = static_cast<int64_t>(value->height);
obj["mode"] = (value->mode == State::Online) ? "Online" : "Bootstrapping"; obj["mode"] = (value->mode == State::Online) ? "Online" : "Bootstrapping";
const OperationStatus free_status = free_cryptarchia_info(value); OperationStatus free_status = free_cryptarchia_info(value);
if (!is_ok(&free_status)) { if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free cryptarchia info. Error: %d\n", free_status); fprintf(stderr, "Failed to free cryptarchia info: %s\n", operation_status::take_message(free_status).c_str());
} }
return result::ok(obj.dump()); return result::ok(obj.dump());
} }

View File

@ -26,7 +26,15 @@ public:
[[nodiscard]] StdLogosResult stop(); [[nodiscard]] StdLogosResult stop();
// Config management // 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) const;
[[nodiscard]] static StdLogosResult update_user_config( [[nodiscard]] static StdLogosResult update_user_config(
const std::string& user_config_path, const std::string& user_config_path,
const std::string& keystore_path const std::string& keystore_path
@ -122,10 +130,8 @@ public:
// Blend // Blend
[[nodiscard]] StdLogosResult blend_join_as_core_node( [[nodiscard]] StdLogosResult blend_join_as_core_node(
const std::string& provider_id_hex, const std::string& locator,
const std::string& zk_id_hex, const std::string& locked_note_id_hex
const std::string& locked_note_id_hex,
const std::vector<std::string>& locators
) const; ) const;
// Explorer // Explorer

View File

@ -6,6 +6,15 @@
#include <logos_blockchain.h> #include <logos_blockchain.h>
#include <cstring> #include <cstring>
#include <cstdlib> #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 char s_fakeNode = 0;
static CryptarchiaInfo s_fakeCryptarchiaInfo = {}; static CryptarchiaInfo s_fakeCryptarchiaInfo = {};
@ -18,15 +27,26 @@ static uint8_t s_mockAddr3[32];
static uint8_t* s_mockAddrs[] = { s_mockAddr0, s_mockAddr1, s_mockAddr2, s_mockAddr3 }; static uint8_t* s_mockAddrs[] = { s_mockAddr0, s_mockAddr1, s_mockAddr2, s_mockAddr3 };
static ClaimableVoucher s_mockClaimableVouchers[4]; static ClaimableVoucher s_mockClaimableVouchers[4];
static OperationStatus make_status(int code) {
OperationStatus s;
s.code = static_cast<OperationStatusCode>(code);
s.message = code != 0 ? strdup("mock error") : nullptr;
return s;
}
extern "C" { extern "C" {
bool is_ok(const OperationStatus* status) { bool is_ok(const OperationStatus* status) {
return status && *status == 0; return status && status->code == OperationStatusCode_Ok;
} }
OperationStatus generate_user_config(GenerateConfigArgs args) { OperationStatus generate_user_config(GenerateConfigArgs args) {
LOGOS_CMOCK_RECORD("generate_user_config"); LOGOS_CMOCK_RECORD("generate_user_config");
return LOGOS_CMOCK_RETURN(int, "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 make_status(LOGOS_CMOCK_RETURN(int, "generate_user_config"));
} }
NodeResult start_lb_node(const char* config_path, const char* deployment) { NodeResult start_lb_node(const char* config_path, const char* deployment) {
@ -34,23 +54,23 @@ NodeResult start_lb_node(const char* config_path, const char* deployment) {
int ok = LOGOS_CMOCK_RETURN(int, "start_lb_node"); int ok = LOGOS_CMOCK_RETURN(int, "start_lb_node");
NodeResult result; NodeResult result;
result.value = ok ? reinterpret_cast<LogosBlockchainNode*>(&s_fakeNode) : nullptr; result.value = ok ? reinterpret_cast<LogosBlockchainNode*>(&s_fakeNode) : nullptr;
result.error = ok ? 0 : 1; result.error = make_status(ok ? 0 : 1);
return result; return result;
} }
OperationStatus stop_node(LogosBlockchainNode* node) { OperationStatus shutdown_node(LogosBlockchainNode* node) {
LOGOS_CMOCK_RECORD("stop_node"); LOGOS_CMOCK_RECORD("shutdown_node");
return 0; return make_status(0);
} }
OperationStatus update_user_config(const char* user_config_path, const char* keystore_path) { OperationStatus update_user_config(const char* user_config_path, const char* keystore_path) {
LOGOS_CMOCK_RECORD("update_user_config"); LOGOS_CMOCK_RECORD("update_user_config");
return LOGOS_CMOCK_RETURN(int, "update_user_config"); return make_status(LOGOS_CMOCK_RETURN(int, "update_user_config"));
} }
OperationStatus migrate_user_config(const char* output_path, const char* keystore_path) { OperationStatus migrate_user_config(const char* output_path, const char* keystore_path) {
LOGOS_CMOCK_RECORD("migrate_user_config"); LOGOS_CMOCK_RECORD("migrate_user_config");
return LOGOS_CMOCK_RETURN(int, "migrate_user_config"); return make_status(LOGOS_CMOCK_RETURN(int, "migrate_user_config"));
} }
OperationStatus migrate_user_config_0_1_2( OperationStatus migrate_user_config_0_1_2(
@ -59,7 +79,7 @@ OperationStatus migrate_user_config_0_1_2(
const char* keystore_path) const char* keystore_path)
{ {
LOGOS_CMOCK_RECORD("migrate_user_config_0_1_2"); LOGOS_CMOCK_RECORD("migrate_user_config_0_1_2");
return LOGOS_CMOCK_RETURN(int, "migrate_user_config_0_1_2"); return make_status(LOGOS_CMOCK_RETURN(int, "migrate_user_config_0_1_2"));
} }
OperationStatus participate( OperationStatus participate(
@ -69,7 +89,7 @@ OperationStatus participate(
const char* external_address) const char* external_address)
{ {
LOGOS_CMOCK_RECORD("participate"); LOGOS_CMOCK_RECORD("participate");
return LOGOS_CMOCK_RETURN(int, "participate"); return make_status(LOGOS_CMOCK_RETURN(int, "participate"));
} }
StringResult generate_key( StringResult generate_key(
@ -82,7 +102,7 @@ StringResult generate_key(
StringResult result; StringResult result;
const char* id = LOGOS_CMOCK_RETURN_STRING("generate_key"); const char* id = LOGOS_CMOCK_RETURN_STRING("generate_key");
result.value = id ? strdup(id) : nullptr; result.value = id ? strdup(id) : nullptr;
result.error = LOGOS_CMOCK_RETURN(int, "generate_key_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "generate_key_error"));
return result; return result;
} }
@ -94,7 +114,7 @@ OperationStatus add_key(
const char* key_title) const char* key_title)
{ {
LOGOS_CMOCK_RECORD("add_key"); LOGOS_CMOCK_RECORD("add_key");
return LOGOS_CMOCK_RETURN(int, "add_key"); return make_status(LOGOS_CMOCK_RETURN(int, "add_key"));
} }
OperationStatus remove_key( OperationStatus remove_key(
@ -103,7 +123,7 @@ OperationStatus remove_key(
const char* key_title) const char* key_title)
{ {
LOGOS_CMOCK_RECORD("remove_key"); LOGOS_CMOCK_RECORD("remove_key");
return LOGOS_CMOCK_RETURN(int, "remove_key"); return make_status(LOGOS_CMOCK_RETURN(int, "remove_key"));
} }
StringResult get_peer_id(const char* config_path) { StringResult get_peer_id(const char* config_path) {
@ -111,20 +131,20 @@ StringResult get_peer_id(const char* config_path) {
StringResult result; StringResult result;
const char* id = LOGOS_CMOCK_RETURN_STRING("get_peer_id"); const char* id = LOGOS_CMOCK_RETURN_STRING("get_peer_id");
result.value = id ? strdup(id) : nullptr; result.value = id ? strdup(id) : nullptr;
result.error = LOGOS_CMOCK_RETURN(int, "get_peer_id_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "get_peer_id_error"));
return result; return result;
} }
OperationStatus subscribe_to_new_blocks(LogosBlockchainNode* node, BlockCallback callback) { OperationStatus subscribe_to_new_blocks(LogosBlockchainNode* node, BlockCallback callback) {
LOGOS_CMOCK_RECORD("subscribe_to_new_blocks"); LOGOS_CMOCK_RECORD("subscribe_to_new_blocks");
return LOGOS_CMOCK_RETURN(int, "subscribe_to_new_blocks"); return make_status(LOGOS_CMOCK_RETURN(int, "subscribe_to_new_blocks"));
} }
BalanceResult get_balance(LogosBlockchainNode* node, const uint8_t* address, const void* reserved) { BalanceResult get_balance(LogosBlockchainNode* node, const uint8_t* address, const void* reserved) {
LOGOS_CMOCK_RECORD("get_balance"); LOGOS_CMOCK_RECORD("get_balance");
BalanceResult result; BalanceResult result;
result.value = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "get_balance_value")); result.value = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "get_balance_value"));
result.error = LOGOS_CMOCK_RETURN(int, "get_balance_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "get_balance_error"));
return result; return result;
} }
@ -132,7 +152,7 @@ TransferHashResult transfer_funds(LogosBlockchainNode* node, const TransferFunds
LOGOS_CMOCK_RECORD("transfer_funds"); LOGOS_CMOCK_RECORD("transfer_funds");
TransferHashResult result; TransferHashResult result;
memset(result.value, 0xAB, sizeof(Hash)); memset(result.value, 0xAB, sizeof(Hash));
result.error = LOGOS_CMOCK_RETURN(int, "transfer_funds_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "transfer_funds_error"));
return result; return result;
} }
@ -140,7 +160,7 @@ FfiLeaderClaimResult leader_claim(LogosBlockchainNode* node) {
LOGOS_CMOCK_RECORD("leader_claim"); LOGOS_CMOCK_RECORD("leader_claim");
FfiLeaderClaimResult result; FfiLeaderClaimResult result;
memset(result.value, 0xEF, sizeof(TxHash)); memset(result.value, 0xEF, sizeof(TxHash));
result.error = LOGOS_CMOCK_RETURN(int, "leader_claim_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "leader_claim_error"));
return result; return result;
} }
@ -148,7 +168,7 @@ KnownAddressesResult get_known_addresses(LogosBlockchainNode* node) {
LOGOS_CMOCK_RECORD("get_known_addresses"); LOGOS_CMOCK_RECORD("get_known_addresses");
KnownAddressesResult result; KnownAddressesResult result;
int err = LOGOS_CMOCK_RETURN(int, "get_known_addresses_error"); int err = LOGOS_CMOCK_RETURN(int, "get_known_addresses_error");
result.error = err; result.error = make_status(err);
if (err == 0) { if (err == 0) {
int count = LOGOS_CMOCK_RETURN(int, "get_known_addresses_count"); int count = LOGOS_CMOCK_RETURN(int, "get_known_addresses_count");
if (count > 4) count = 4; if (count > 4) count = 4;
@ -167,14 +187,14 @@ KnownAddressesResult get_known_addresses(LogosBlockchainNode* node) {
OperationStatus free_known_addresses(KnownAddresses addrs) { OperationStatus free_known_addresses(KnownAddresses addrs) {
LOGOS_CMOCK_RECORD("free_known_addresses"); LOGOS_CMOCK_RECORD("free_known_addresses");
return 0; return make_status(0);
} }
FfiClaimableVouchersResult get_claimable_vouchers(LogosBlockchainNode* node, const HeaderId* optional_tip) { FfiClaimableVouchersResult get_claimable_vouchers(LogosBlockchainNode* node, const HeaderId* optional_tip) {
LOGOS_CMOCK_RECORD("get_claimable_vouchers"); LOGOS_CMOCK_RECORD("get_claimable_vouchers");
FfiClaimableVouchersResult result; FfiClaimableVouchersResult result;
int err = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_error"); int err = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_error");
result.error = err; result.error = make_status(err);
if (err == 0) { if (err == 0) {
int count = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_count"); int count = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_count");
if (count > 4) count = 4; if (count > 4) count = 4;
@ -195,7 +215,7 @@ FfiClaimableVouchersResult get_claimable_vouchers(LogosBlockchainNode* node, con
OperationStatus free_claimable_vouchers(ClaimableVouchers vouchers) { OperationStatus free_claimable_vouchers(ClaimableVouchers vouchers) {
LOGOS_CMOCK_RECORD("free_claimable_vouchers"); LOGOS_CMOCK_RECORD("free_claimable_vouchers");
return 0; return make_status(0);
} }
// Wallet-notes mock storage (up to 4 notes) // Wallet-notes mock storage (up to 4 notes)
@ -210,7 +230,7 @@ FfiWalletNotesResult get_wallet_notes(
FfiWalletNotesResult result; FfiWalletNotesResult result;
memset(&result.value, 0, sizeof(WalletNotes)); memset(&result.value, 0, sizeof(WalletNotes));
int err = LOGOS_CMOCK_RETURN(int, "get_wallet_notes_error"); int err = LOGOS_CMOCK_RETURN(int, "get_wallet_notes_error");
result.error = err; result.error = make_status(err);
if (err == 0) { if (err == 0) {
int count = LOGOS_CMOCK_RETURN(int, "get_wallet_notes_count"); int count = LOGOS_CMOCK_RETURN(int, "get_wallet_notes_count");
if (count > 4) count = 4; if (count > 4) count = 4;
@ -228,14 +248,14 @@ FfiWalletNotesResult get_wallet_notes(
OperationStatus free_wallet_notes(WalletNotes notes) { OperationStatus free_wallet_notes(WalletNotes notes) {
LOGOS_CMOCK_RECORD("free_wallet_notes"); LOGOS_CMOCK_RECORD("free_wallet_notes");
return 0; return make_status(0);
} }
FfiChannelDepositResult channel_deposit(LogosBlockchainNode* node, const ChannelDepositArguments* arguments) { FfiChannelDepositResult channel_deposit(LogosBlockchainNode* node, const ChannelDepositArguments* arguments) {
LOGOS_CMOCK_RECORD("channel_deposit"); LOGOS_CMOCK_RECORD("channel_deposit");
FfiChannelDepositResult result; FfiChannelDepositResult result;
memset(result.value, 0xBC, sizeof(Hash)); memset(result.value, 0xBC, sizeof(Hash));
result.error = LOGOS_CMOCK_RETURN(int, "channel_deposit_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "channel_deposit_error"));
return result; return result;
} }
@ -246,22 +266,19 @@ FfiChannelDepositResult channel_deposit_with_notes(
LOGOS_CMOCK_RECORD("channel_deposit_with_notes"); LOGOS_CMOCK_RECORD("channel_deposit_with_notes");
FfiChannelDepositResult result; FfiChannelDepositResult result;
memset(result.value, 0xDE, sizeof(Hash)); memset(result.value, 0xDE, sizeof(Hash));
result.error = LOGOS_CMOCK_RETURN(int, "channel_deposit_with_notes_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "channel_deposit_with_notes_error"));
return result; return result;
} }
BlendHashResult blend_join_as_core_node( BlendHashResult blend_join_as_core_node(
LogosBlockchainNode* node, LogosBlockchainNode* node,
const uint8_t* provider_id, const char* locator,
const uint8_t* zk_id, const uint8_t* locked_note_id)
const uint8_t* locked_note_id,
const char** locators,
size_t locators_count)
{ {
LOGOS_CMOCK_RECORD("blend_join_as_core_node"); LOGOS_CMOCK_RECORD("blend_join_as_core_node");
BlendHashResult result; BlendHashResult result;
memset(result.value, 0xCD, sizeof(Hash)); memset(result.value, 0xCD, sizeof(Hash));
result.error = LOGOS_CMOCK_RETURN(int, "blend_join_as_core_node_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "blend_join_as_core_node_error"));
return result; return result;
} }
@ -270,7 +287,7 @@ StringResult get_block(LogosBlockchainNode* node, const HeaderId* header_id) {
StringResult result; StringResult result;
const char* json = LOGOS_CMOCK_RETURN_STRING("get_block"); const char* json = LOGOS_CMOCK_RETURN_STRING("get_block");
result.value = json ? strdup(json) : nullptr; result.value = json ? strdup(json) : nullptr;
result.error = LOGOS_CMOCK_RETURN(int, "get_block_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "get_block_error"));
return result; return result;
} }
@ -279,7 +296,7 @@ StringResult get_blocks(LogosBlockchainNode* node, uint64_t from_slot, uint64_t
StringResult result; StringResult result;
const char* json = LOGOS_CMOCK_RETURN_STRING("get_blocks"); const char* json = LOGOS_CMOCK_RETURN_STRING("get_blocks");
result.value = json ? strdup(json) : nullptr; result.value = json ? strdup(json) : nullptr;
result.error = LOGOS_CMOCK_RETURN(int, "get_blocks_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "get_blocks_error"));
return result; return result;
} }
@ -288,7 +305,7 @@ StringResult get_transaction(LogosBlockchainNode* node, const TxHash* tx_hash) {
StringResult result; StringResult result;
const char* json = LOGOS_CMOCK_RETURN_STRING("get_transaction"); const char* json = LOGOS_CMOCK_RETURN_STRING("get_transaction");
result.value = json ? strdup(json) : nullptr; result.value = json ? strdup(json) : nullptr;
result.error = LOGOS_CMOCK_RETURN(int, "get_transaction_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "get_transaction_error"));
return result; return result;
} }
@ -302,19 +319,19 @@ CryptarchiaInfoResult get_cryptarchia_info(LogosBlockchainNode* node) {
memset(s_fakeCryptarchiaInfo.lib, 0xEE, 32); memset(s_fakeCryptarchiaInfo.lib, 0xEE, 32);
memset(s_fakeCryptarchiaInfo.tip, 0xFF, 32); memset(s_fakeCryptarchiaInfo.tip, 0xFF, 32);
result.value = &s_fakeCryptarchiaInfo; result.value = &s_fakeCryptarchiaInfo;
result.error = LOGOS_CMOCK_RETURN(int, "get_cryptarchia_info_error"); result.error = make_status(LOGOS_CMOCK_RETURN(int, "get_cryptarchia_info_error"));
return result; return result;
} }
OperationStatus free_cryptarchia_info(CryptarchiaInfo* info) { OperationStatus free_cryptarchia_info(CryptarchiaInfo* info) {
LOGOS_CMOCK_RECORD("free_cryptarchia_info"); LOGOS_CMOCK_RECORD("free_cryptarchia_info");
return 0; return make_status(0);
} }
OperationStatus free_cstring(char* s) { OperationStatus free_cstring(char* s) {
LOGOS_CMOCK_RECORD("free_cstring"); LOGOS_CMOCK_RECORD("free_cstring");
free(s); free(s);
return 0; return make_status(0);
} }
} // extern "C" } // extern "C"

View File

@ -21,8 +21,28 @@ typedef Hash NoteId;
// Opaque node handle // Opaque node handle
typedef struct LogosBlockchainNode LogosBlockchainNode; typedef struct LogosBlockchainNode LogosBlockchainNode;
// Operation status (0 = OK) // Operation status code (0 = OK)
typedef int OperationStatus; typedef enum OperationStatusCode {
OperationStatusCode_Ok = 0,
OperationStatusCode_NotFound = 1,
OperationStatusCode_NullPointer = 2,
OperationStatusCode_RelayError = 3,
OperationStatusCode_ChannelSendError = 4,
OperationStatusCode_ChannelReceiveError = 5,
OperationStatusCode_ServiceError = 6,
OperationStatusCode_RuntimeError = 7,
OperationStatusCode_DynError = 8,
OperationStatusCode_InitializationError = 9,
OperationStatusCode_StopError = 10,
OperationStatusCode_ConfigurationError = 11,
OperationStatusCode_ValidationError = 12,
} OperationStatusCode;
// Operation status: code + Rust-allocated error message (free with free_cstring).
typedef struct {
OperationStatusCode code;
char* message;
} OperationStatus;
// Consensus state enum // Consensus state enum
typedef enum { Bootstrapping, Online } State; typedef enum { Bootstrapping, Online } State;
@ -40,7 +60,9 @@ typedef struct {
const char* http_addr; const char* http_addr;
const char* external_address; const char* external_address;
const char* state_path; const char* state_path;
const bool* ibd; const char* storage_path;
const char* logs_path;
const bool* skip_ibd;
const char* log_filter; const char* log_filter;
const char* kms_file; const char* kms_file;
} GenerateConfigArgs; } GenerateConfigArgs;
@ -140,7 +162,7 @@ bool is_ok(const OperationStatus* status);
// Lifecycle // Lifecycle
OperationStatus generate_user_config(GenerateConfigArgs args); OperationStatus generate_user_config(GenerateConfigArgs args);
NodeResult start_lb_node(const char* config_path, const char* deployment); NodeResult start_lb_node(const char* config_path, const char* deployment);
OperationStatus stop_node(LogosBlockchainNode* node); OperationStatus shutdown_node(LogosBlockchainNode* node);
OperationStatus subscribe_to_new_blocks(LogosBlockchainNode* node, BlockCallback callback); OperationStatus subscribe_to_new_blocks(LogosBlockchainNode* node, BlockCallback callback);
// Config management // Config management
@ -199,11 +221,8 @@ OperationStatus free_claimable_vouchers(ClaimableVouchers vouchers);
// Blend // Blend
BlendHashResult blend_join_as_core_node( BlendHashResult blend_join_as_core_node(
LogosBlockchainNode* node, LogosBlockchainNode* node,
const uint8_t* provider_id, const char* locator,
const uint8_t* zk_id, const uint8_t* locked_note_id);
const uint8_t* locked_note_id,
const char** locators,
size_t locators_count);
// Explorer // Explorer
StringResult get_block(LogosBlockchainNode* node, const HeaderId* header_id); StringResult get_block(LogosBlockchainNode* node, const HeaderId* header_id);

View File

@ -6,7 +6,6 @@
#include <cstdlib> #include <cstdlib>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <string> #include <string>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
@ -40,16 +39,7 @@ struct TempDir {
}; };
// Helper: create a module with a running (mocked) node. // Helper: create a module with a running (mocked) node.
// Sets up circuits directory, LOGOS_MODULE_PATH env, and calls start().
static LogosBlockchainModule* createStartedModule(LogosTestContext& t, TempDir& tmpDir) { static LogosBlockchainModule* createStartedModule(LogosTestContext& t, TempDir& tmpDir) {
fs::create_directories(tmpDir.path / "circuits");
{
std::ofstream f((tmpDir.path / "circuits" / "dummy.bin").string());
f << "x";
}
setenv("LOGOS_MODULE_PATH", tmpDir.path.string().c_str(), 1);
auto* module = new LogosBlockchainModule(); auto* module = new LogosBlockchainModule();
t.mockCFunction("start_lb_node").returns(1); t.mockCFunction("start_lb_node").returns(1);
@ -96,6 +86,122 @@ LOGOS_TEST(generate_user_config_from_json_string) {
LOGOS_ASSERT(t.cFunctionCalled("generate_user_config")); 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) { LOGOS_TEST(generate_user_config_with_all_fields) {
auto t = LogosTestContext("blockchain_module"); auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module; LogosBlockchainModule module;
@ -186,7 +292,7 @@ LOGOS_TEST(wallet_get_known_addresses_without_node_returns_error) {
LOGOS_TEST(blend_join_as_core_node_without_node_returns_error) { LOGOS_TEST(blend_join_as_core_node_without_node_returns_error) {
auto t = LogosTestContext("blockchain_module"); auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module; LogosBlockchainModule module;
StdLogosResult result = module.blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, {"locator1"}); StdLogosResult result = module.blend_join_as_core_node("locator1", VALID_HEX);
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "not running")); LOGOS_ASSERT_TRUE(contains(result.error, "not running"));
} }
@ -248,7 +354,7 @@ LOGOS_TEST(stop_succeeds_with_running_node) {
LOGOS_ASSERT_TRUE(module != nullptr); LOGOS_ASSERT_TRUE(module != nullptr);
LOGOS_ASSERT_TRUE(module->stop().success); LOGOS_ASSERT_TRUE(module->stop().success);
LOGOS_ASSERT(t.cFunctionCalled("stop_node")); LOGOS_ASSERT(t.cFunctionCalled("shutdown_node"));
delete module; delete module;
} }
@ -369,27 +475,15 @@ LOGOS_TEST(wallet_transfer_funds_rejects_invalid_optional_tip) {
// blend_join_as_core_node validation // blend_join_as_core_node validation
LOGOS_TEST(blend_join_rejects_invalid_provider_id) { LOGOS_TEST(blend_join_rejects_empty_locator) {
auto t = LogosTestContext("blockchain_module"); auto t = LogosTestContext("blockchain_module");
TempDir tmpDir; TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir); auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr); LOGOS_ASSERT_TRUE(module != nullptr);
StdLogosResult result = module->blend_join_as_core_node("short", VALID_HEX, VALID_HEX, {}); StdLogosResult result = module->blend_join_as_core_node("", VALID_HEX);
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "provider_id")); LOGOS_ASSERT_TRUE(contains(result.error, "locator"));
delete module;
}
LOGOS_TEST(blend_join_rejects_invalid_zk_id) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
StdLogosResult result = module->blend_join_as_core_node(VALID_HEX, "short", VALID_HEX, {});
LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "zk_id"));
delete module; delete module;
} }
@ -399,7 +493,7 @@ LOGOS_TEST(blend_join_rejects_invalid_locked_note_id) {
auto* module = createStartedModule(t, tmpDir); auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr); LOGOS_ASSERT_TRUE(module != nullptr);
StdLogosResult result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, "short", {}); StdLogosResult result = module->blend_join_as_core_node("locator1", "short");
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "locked_note_id")); LOGOS_ASSERT_TRUE(contains(result.error, "locked_note_id"));
delete module; delete module;
@ -556,7 +650,6 @@ LOGOS_TEST(leader_claim_returns_error_on_ffi_failure) {
StdLogosResult result = module->leader_claim(); StdLogosResult result = module->leader_claim();
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "Failed to claim leader rewards"));
delete module; delete module;
} }
@ -601,7 +694,7 @@ LOGOS_TEST(channel_deposit_returns_error_on_ffi_failure) {
StdLogosResult result = module->channel_deposit(VALID_HEX, VALID_HEX, "100", "", ""); StdLogosResult result = module->channel_deposit(VALID_HEX, VALID_HEX, "100", "", "");
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "Failed to deposit into channel")); LOGOS_ASSERT_TRUE(contains(result.error, "mock error"));
delete module; delete module;
} }
@ -723,7 +816,7 @@ LOGOS_TEST(wallet_get_notes_returns_error_on_ffi_failure) {
StdLogosResult result = module->wallet_get_notes(VALID_HEX, ""); StdLogosResult result = module->wallet_get_notes(VALID_HEX, "");
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "Failed to get wallet notes")); LOGOS_ASSERT_TRUE(contains(result.error, "mock error"));
delete module; delete module;
} }
@ -768,7 +861,7 @@ LOGOS_TEST(channel_deposit_with_notes_returns_error_on_ffi_failure) {
StdLogosResult result = module->channel_deposit_with_notes( StdLogosResult result = module->channel_deposit_with_notes(
VALID_HEX, {VALID_HEX}, "", VALID_HEX, {VALID_HEX}, "0", ""); VALID_HEX, {VALID_HEX}, "", VALID_HEX, {VALID_HEX}, "0", "");
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "Failed to deposit into channel")); LOGOS_ASSERT_TRUE(contains(result.error, "mock error"));
delete module; delete module;
} }
@ -916,7 +1009,7 @@ LOGOS_TEST(wallet_get_claimable_vouchers_returns_error_on_ffi_failure) {
StdLogosResult result = module->wallet_get_claimable_vouchers(); StdLogosResult result = module->wallet_get_claimable_vouchers();
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
LOGOS_ASSERT_TRUE(contains(result.error, "Failed to get claimable vouchers")); LOGOS_ASSERT_TRUE(contains(result.error, "mock error"));
delete module; delete module;
} }
@ -930,8 +1023,7 @@ LOGOS_TEST(blend_join_as_core_node_returns_declaration_id) {
t.mockCFunction("blend_join_as_core_node_error").returns(0); t.mockCFunction("blend_join_as_core_node_error").returns(0);
std::vector<std::string> locators = {"locator1", "locator2"}; StdLogosResult result = module->blend_join_as_core_node("locator1", VALID_HEX);
StdLogosResult result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, locators);
LOGOS_ASSERT_TRUE(result.success); LOGOS_ASSERT_TRUE(result.success);
// Mock fills hash with 0xCD -> hex "cdcd...cd" (64 chars) // Mock fills hash with 0xCD -> hex "cdcd...cd" (64 chars)
std::string declarationId = result.value.get<std::string>(); std::string declarationId = result.value.get<std::string>();
@ -949,7 +1041,7 @@ LOGOS_TEST(blend_join_as_core_node_returns_error_on_ffi_failure) {
t.mockCFunction("blend_join_as_core_node_error").returns(1); t.mockCFunction("blend_join_as_core_node_error").returns(1);
StdLogosResult result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, {}); StdLogosResult result = module->blend_join_as_core_node("locator1", VALID_HEX);
LOGOS_ASSERT_FALSE(result.success); LOGOS_ASSERT_FALSE(result.success);
delete module; delete module;
} }