Merge remote-tracking branch 'origin/master' into doctests-init-suggestion

This commit is contained in:
Daniel 2026-06-18 16:11:25 +02:00
commit 1f0e21b040
7 changed files with 1372 additions and 438 deletions

936
flake.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,9 @@
description = "Logos Blockchain Module - Qt6 Plugin";
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
# v0.1.3-rc.10-compatible + rust-rapidsnark nix fixes + cli commands + leader_claim C binding
logos-blockchain.url = "github:logos-blockchain/logos-blockchain?ref=4efdf816e2a3d1447096c3f64c9e230769eb4ecb";
logos-module-builder.url = "github:logos-co/logos-module-builder?ref=bc868bf47f9d797637ac78af814b0db718dde4b8";
# v0.1.3-rc.10-compatible + rust-rapidsnark nix fixes + cli commands + leader_claim + channel deposit / wallet notes C bindings
logos-blockchain.url = "github:logos-blockchain/logos-blockchain?ref=0222706b14010fbdfbe9a94d3617ebf46a77fdd1";
};
outputs = inputs@{ logos-module-builder, ... }:

View File

@ -48,6 +48,27 @@ namespace {
}
}
// Parse arbitrary-length hex (optional 0x prefix) into bytes. Unlike
// parse_address_hex this does not enforce a fixed length; used for the
// variable-length channel deposit metadata. Returns false on odd length or
// non-hex input.
bool parse_hex_bytes(const std::string& hex_in, std::vector<uint8_t>& out) {
std::string hex = hex_in;
boost::algorithm::trim(hex);
if (hex.size() >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X'))
hex = hex.substr(2);
if (hex.size() % 2 != 0)
return false;
try {
std::string decoded;
boost::algorithm::unhex(hex.begin(), hex.end(), std::back_inserter(decoded));
out.assign(decoded.begin(), decoded.end());
return true;
} catch (const boost::algorithm::non_hex_input&) {
return false;
}
}
std::string bytes_to_hex(const uint8_t* data, size_t len) {
std::string out;
out.reserve(len * 2);
@ -82,10 +103,10 @@ namespace {
uint16_t blend_port_val;
std::string http_addr_data;
std::string external_address_data;
bool no_public_ip_check_val;
std::string custom_deployment_config_path_data;
Deployment deployment_val{};
std::string state_path_data;
bool ibd_val;
std::string log_filter_data;
std::string kms_file_data;
// The FFI struct with pointers into owned data
GenerateConfigArgs ffi_args{};
@ -150,39 +171,6 @@ namespace {
ffi_args.external_address = nullptr;
}
// no_public_ip_check (bool -> const bool*)
if (args.contains("no_public_ip_check") && args["no_public_ip_check"].is_boolean()) {
no_public_ip_check_val = args["no_public_ip_check"].get<bool>();
ffi_args.no_public_ip_check = &no_public_ip_check_val;
} else {
ffi_args.no_public_ip_check = nullptr;
}
// deployment (const struct Deployment*)
// Expected format: { "deployment": { "well_known_deployment": "devnet" } }
// OR: { "deployment": { "config_path": "/path/to/config" } }
if (args.contains("deployment") && args["deployment"].is_object()) {
const auto& deployment = args["deployment"];
if (deployment.contains("well_known_deployment") && deployment["well_known_deployment"].is_string()) {
deployment_val.deployment_type = DeploymentType::WellKnown;
const std::string wellknown = deployment["well_known_deployment"].get<std::string>();
if (wellknown == "devnet") {
deployment_val.well_known_deployment = WellKnownDeployment::Devnet;
}
deployment_val.custom_deployment_config_path = nullptr;
} else if (deployment.contains("config_path") && deployment["config_path"].is_string()) {
deployment_val.deployment_type = DeploymentType::Custom;
deployment_val.well_known_deployment = static_cast<WellKnownDeployment>(0);
custom_deployment_config_path_data = deployment["config_path"].get<std::string>();
deployment_val.custom_deployment_config_path = custom_deployment_config_path_data.c_str();
}
ffi_args.deployment = &deployment_val;
} else {
ffi_args.deployment = nullptr;
}
// state_path (string -> const char*)
if (args.contains("state_path") && args["state_path"].is_string()) {
state_path_data = args["state_path"].get<std::string>();
@ -190,6 +178,30 @@ namespace {
} else {
ffi_args.state_path = nullptr;
}
// ibd (bool -> const bool*)
if (args.contains("ibd") && args["ibd"].is_boolean()) {
ibd_val = args["ibd"].get<bool>();
ffi_args.ibd = &ibd_val;
} else {
ffi_args.ibd = nullptr;
}
// log_filter (string -> const char*)
if (args.contains("log_filter") && args["log_filter"].is_string()) {
log_filter_data = args["log_filter"].get<std::string>();
ffi_args.log_filter = log_filter_data.c_str();
} else {
ffi_args.log_filter = nullptr;
}
// kms_file (string -> const char*)
if (args.contains("kms_file") && args["kms_file"].is_string()) {
kms_file_data = args["kms_file"].get<std::string>();
ffi_args.kms_file = kms_file_data.c_str();
} else {
ffi_args.kms_file = nullptr;
}
}
};
} // namespace
@ -247,13 +259,13 @@ LogosBlockchainModule::~LogosBlockchainModule() {
// Lifecycle
int LogosBlockchainModule::generate_user_config(const std::string& json_args) {
std::string LogosBlockchainModule::generate_user_config(const std::string& json_args) {
json parsed_args;
try {
parsed_args = json::parse(json_args);
} catch (const json::parse_error& e) {
fprintf(stderr, "Failed to parse JSON args: %s\n", e.what());
return 1;
return "1";
}
const OwnedGenerateConfigArgs owned_args(parsed_args);
@ -261,16 +273,16 @@ int LogosBlockchainModule::generate_user_config(const std::string& json_args) {
const OperationStatus status = ::generate_user_config(owned_args.ffi_args);
if (!is_ok(&status)) {
fprintf(stderr, "Failed to generate user config. Error: %d\n", status);
return 1;
return "1";
}
return 0;
return "0";
}
int LogosBlockchainModule::start(const std::string& config_path, const std::string& deployment) {
std::string LogosBlockchainModule::start(const std::string& config_path, const std::string& deployment) {
if (node) {
fprintf(stderr, "Could not execute the operation: The node is already running.\n");
return 1;
return "1";
}
const char* module_path_env = std::getenv("LOGOS_MODULE_PATH");
@ -289,7 +301,7 @@ int LogosBlockchainModule::start(const std::string& config_path, const std::stri
fprintf(stderr, "Using config from LB_CONFIG_PATH: %s\n", effective_config_path.c_str());
} else {
fprintf(stderr, "Config path was not specified and LB_CONFIG_PATH is not set.\n");
return 3;
return "3";
}
}
@ -303,7 +315,7 @@ int LogosBlockchainModule::start(const std::string& config_path, const std::stri
fprintf(stderr, "Start node returned with value and error.\n");
if (!is_ok(&error)) {
fprintf(stderr, "Failed to start the node. Error: %d\n", error);
return 4;
return "4";
}
node = value;
@ -311,23 +323,23 @@ int LogosBlockchainModule::start(const std::string& config_path, const std::stri
if (!node) {
fprintf(stderr, "Could not subscribe to block events: The node is not running.\n");
return 4;
return "4";
}
s_instance = this;
const OperationStatus subscribe_status = subscribe_to_new_blocks(node, on_new_block_callback);
if (!is_ok(&subscribe_status)) {
fprintf(stderr, "Failed to subscribe to new blocks. Error: %d\n", subscribe_status);
return 5;
return "5";
}
return 0;
return "0";
}
int LogosBlockchainModule::stop() {
std::string LogosBlockchainModule::stop() {
if (!node) {
fprintf(stderr, "Could not execute the operation: The node is not running.\n");
return 1;
return "1";
}
s_instance = nullptr;
@ -340,36 +352,36 @@ int LogosBlockchainModule::stop() {
}
node = nullptr;
return 0;
return "0";
}
// Config management
int LogosBlockchainModule::update_user_config(const std::string& user_config_path, const std::string& keystore_path) {
std::string LogosBlockchainModule::update_user_config(const std::string& user_config_path, const std::string& keystore_path) {
const std::string config = localPathFromFileUrl(user_config_path);
const std::string keystore = localPathFromFileUrl(keystore_path);
const OperationStatus status = ::update_user_config(config.c_str(), keystore.c_str());
if (!is_ok(&status)) {
fprintf(stderr, "Failed to update user config. Error: %d\n", status);
return 1;
return "1";
}
return 0;
return "0";
}
int LogosBlockchainModule::migrate_user_config(const std::string& output_path, const std::string& keystore_path) {
std::string LogosBlockchainModule::migrate_user_config(const std::string& output_path, const std::string& keystore_path) {
const std::string output = localPathFromFileUrl(output_path);
const std::string keystore = localPathFromFileUrl(keystore_path);
const OperationStatus status = ::migrate_user_config(output.c_str(), keystore.c_str());
if (!is_ok(&status)) {
fprintf(stderr, "Failed to migrate user config. Error: %d\n", status);
return 1;
return "1";
}
return 0;
return "0";
}
int LogosBlockchainModule::migrate_user_config_0_1_2(
std::string LogosBlockchainModule::migrate_user_config_0_1_2(
const std::string& new_config_path,
const std::string& old_config_path,
const std::string& keystore_path
@ -382,12 +394,12 @@ int LogosBlockchainModule::migrate_user_config_0_1_2(
::migrate_user_config_0_1_2(new_config.c_str(), old_config.c_str(), keystore.c_str());
if (!is_ok(&status)) {
fprintf(stderr, "Failed to migrate 0.1.2 config. Error: %d\n", status);
return 1;
return "1";
}
return 0;
return "0";
}
int LogosBlockchainModule::participate(
std::string LogosBlockchainModule::participate(
const std::string& config_path,
const std::string& keystore_path,
const std::string& output_dir,
@ -402,9 +414,9 @@ int LogosBlockchainModule::participate(
::participate(config.c_str(), keystore.c_str(), output.c_str(), external_address_ptr);
if (!is_ok(&status)) {
fprintf(stderr, "Failed to generate participation data. Error: %d\n", status);
return 1;
return "1";
}
return 0;
return "0";
}
// Keystore
@ -438,7 +450,7 @@ std::string LogosBlockchainModule::generate_key(
return result;
}
int LogosBlockchainModule::add_key(
std::string LogosBlockchainModule::add_key(
const std::string& user_config_path,
const std::string& keystore_path,
const std::string& key_type,
@ -448,7 +460,7 @@ int LogosBlockchainModule::add_key(
KeyType type{};
if (!parse_key_type(key_type, type)) {
fprintf(stderr, "Invalid key_type (expected \"ed25519\" or \"zk\").\n");
return 1;
return "1";
}
const std::string config = localPathFromFileUrl(user_config_path);
@ -459,12 +471,12 @@ int LogosBlockchainModule::add_key(
::add_key(config.c_str(), keystore.c_str(), type, key_hex.c_str(), key_title_ptr);
if (!is_ok(&status)) {
fprintf(stderr, "Failed to add key. Error: %d\n", status);
return 1;
return "1";
}
return 0;
return "0";
}
int LogosBlockchainModule::remove_key(
std::string LogosBlockchainModule::remove_key(
const std::string& user_config_path,
const std::string& keystore_path,
const std::string& key_title
@ -475,9 +487,9 @@ int LogosBlockchainModule::remove_key(
const OperationStatus status = ::remove_key(config.c_str(), keystore.c_str(), key_title.c_str());
if (!is_ok(&status)) {
fprintf(stderr, "Failed to remove key. Error: %d\n", status);
return 1;
return "1";
}
return 0;
return "0";
}
// Identity
@ -613,6 +625,54 @@ std::vector<std::string> LogosBlockchainModule::wallet_get_known_addresses() {
return out;
}
std::string LogosBlockchainModule::wallet_get_notes(
const std::string& wallet_address_hex,
const std::string& optional_tip_hex
) {
if (!node) {
return "Error: The node is not running.";
}
const std::vector<uint8_t> address_bytes = parse_address_hex(wallet_address_hex);
if (address_bytes.empty() || static_cast<int>(address_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid wallet address (64 hex characters required).";
}
std::vector<uint8_t> tip_bytes;
const HeaderId* optional_tip = nullptr;
if (!optional_tip_hex.empty()) {
tip_bytes = parse_address_hex(optional_tip_hex);
if (tip_bytes.empty() || static_cast<int>(tip_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid optional tip (64 hex characters or empty).";
}
optional_tip = reinterpret_cast<const HeaderId*>(tip_bytes.data());
}
auto [value, error] = get_wallet_notes(node, address_bytes.data(), optional_tip);
if (!is_ok(&error)) {
return "Error: Failed to get wallet notes: " + std::to_string(error);
}
json obj;
obj["tip"] = bytes_to_hex(reinterpret_cast<const uint8_t*>(value.tip), TX_HASH_BYTES);
json notes = json::array();
for (size_t i = 0; i < value.len; ++i) {
const WalletNote& note = value.notes[i];
json n;
n["id"] = bytes_to_hex(reinterpret_cast<const uint8_t*>(note.id), TX_HASH_BYTES);
// Value is u64; serialized as a string to avoid JSON number precision loss.
n["value"] = std::to_string(note.value);
notes.push_back(std::move(n));
}
obj["notes"] = std::move(notes);
const OperationStatus free_status = free_wallet_notes(value);
if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free wallet notes. Error: %d\n", free_status);
}
return obj.dump();
}
std::string LogosBlockchainModule::leader_claim() {
if (!node) {
return "Error: The node is not running.";
@ -626,6 +686,166 @@ std::string LogosBlockchainModule::leader_claim() {
return bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), TX_HASH_BYTES);
}
// Channel
std::string LogosBlockchainModule::channel_deposit(
const std::string& channel_id_hex,
const std::string& funding_public_key_hex,
const std::string& amount,
const std::string& metadata_hex,
const std::string& optional_tip_hex
) {
if (!node) {
return "Error: The node is not running.";
}
std::string amount_trimmed = amount;
boost::algorithm::trim(amount_trimmed);
uint64_t amount_val = 0;
auto [ptr, ec] = std::from_chars(amount_trimmed.data(), amount_trimmed.data() + amount_trimmed.size(), amount_val);
if (ec != std::errc{} || ptr != amount_trimmed.data() + amount_trimmed.size() || amount_trimmed.empty()) {
return "Error: Invalid amount (positive integer required).";
}
if (amount_val == 0) {
return "Error: Invalid amount (must be greater than zero).";
}
const std::vector<uint8_t> channel_bytes = parse_address_hex(channel_id_hex);
if (channel_bytes.empty() || static_cast<int>(channel_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid channel_id (64 hex characters required).";
}
const std::vector<uint8_t> funding_bytes = parse_address_hex(funding_public_key_hex);
if (funding_bytes.empty() || static_cast<int>(funding_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid funding_public_key (64 hex characters required).";
}
std::vector<uint8_t> metadata_bytes;
if (!metadata_hex.empty() && !parse_hex_bytes(metadata_hex, metadata_bytes)) {
return "Error: Invalid metadata (even-length hex string required).";
}
std::vector<uint8_t> tip_bytes;
const HeaderId* optional_tip = nullptr;
if (!optional_tip_hex.empty()) {
tip_bytes = parse_address_hex(optional_tip_hex);
if (tip_bytes.empty() || static_cast<int>(tip_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid optional tip (64 hex characters or empty).";
}
optional_tip = reinterpret_cast<const HeaderId*>(tip_bytes.data());
}
ChannelDepositArguments args{};
args.optional_tip = optional_tip;
args.channel_id = channel_bytes.data();
args.funding_public_key = funding_bytes.data();
args.amount = amount_val;
args.metadata = metadata_bytes.empty() ? nullptr : metadata_bytes.data();
args.metadata_len = metadata_bytes.size();
auto [value, error] = ::channel_deposit(node, &args);
if (!is_ok(&error)) {
return "Error: Failed to deposit into channel: " + std::to_string(error);
}
return bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES);
}
std::string LogosBlockchainModule::channel_deposit_with_notes(
const std::string& channel_id_hex,
const std::vector<std::string>& input_note_id_hexes,
const std::string& metadata_hex,
const std::string& change_public_key_hex,
const std::vector<std::string>& funding_public_key_hexes,
const std::string& max_tx_fee,
const std::string& optional_tip_hex
) {
if (!node) {
return "Error: The node is not running.";
}
const std::vector<uint8_t> channel_bytes = parse_address_hex(channel_id_hex);
if (channel_bytes.empty() || static_cast<int>(channel_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid channel_id (64 hex characters required).";
}
if (input_note_id_hexes.empty()) {
return "Error: At least one input note required.";
}
// Note IDs are 32-byte values stored contiguously so the buffer can be passed
// as a `NoteId` (uint8_t[32]) array.
std::vector<uint8_t> note_ids_flat;
note_ids_flat.reserve(input_note_id_hexes.size() * ADDRESS_BYTES);
for (const std::string& hex : input_note_id_hexes) {
const std::vector<uint8_t> b = parse_address_hex(hex);
if (b.empty() || static_cast<int>(b.size()) != ADDRESS_BYTES) {
return "Error: Invalid input note id (64 hex characters required).";
}
note_ids_flat.insert(note_ids_flat.end(), b.begin(), b.end());
}
const std::vector<uint8_t> change_bytes = parse_address_hex(change_public_key_hex);
if (change_bytes.empty() || static_cast<int>(change_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid change_public_key (64 hex characters required).";
}
if (funding_public_key_hexes.empty()) {
return "Error: At least one funding public key required.";
}
std::vector<std::vector<uint8_t>> funding_bytes;
for (const std::string& hex : funding_public_key_hexes) {
std::vector<uint8_t> b = parse_address_hex(hex);
if (b.empty() || static_cast<int>(b.size()) != ADDRESS_BYTES) {
return "Error: Invalid funding public key (64 hex characters required).";
}
funding_bytes.push_back(std::move(b));
}
std::vector<const uint8_t*> funding_ptrs;
funding_ptrs.reserve(funding_bytes.size());
for (const auto& b : funding_bytes)
funding_ptrs.push_back(b.data());
std::string fee_trimmed = max_tx_fee;
boost::algorithm::trim(fee_trimmed);
uint64_t max_tx_fee_val = 0;
auto [ptr, ec] = std::from_chars(fee_trimmed.data(), fee_trimmed.data() + fee_trimmed.size(), max_tx_fee_val);
if (ec != std::errc{} || ptr != fee_trimmed.data() + fee_trimmed.size() || fee_trimmed.empty()) {
return "Error: Invalid max_tx_fee (non-negative integer required).";
}
std::vector<uint8_t> metadata_bytes;
if (!metadata_hex.empty() && !parse_hex_bytes(metadata_hex, metadata_bytes)) {
return "Error: Invalid metadata (even-length hex string required).";
}
std::vector<uint8_t> tip_bytes;
const HeaderId* optional_tip = nullptr;
if (!optional_tip_hex.empty()) {
tip_bytes = parse_address_hex(optional_tip_hex);
if (tip_bytes.empty() || static_cast<int>(tip_bytes.size()) != ADDRESS_BYTES) {
return "Error: Invalid optional tip (64 hex characters or empty).";
}
optional_tip = reinterpret_cast<const HeaderId*>(tip_bytes.data());
}
ChannelDepositWithNotesArguments args{};
args.optional_tip = optional_tip;
args.channel_id = channel_bytes.data();
args.input_note_ids = reinterpret_cast<const NoteId*>(note_ids_flat.data());
args.input_note_ids_len = input_note_id_hexes.size();
args.metadata = metadata_bytes.empty() ? nullptr : metadata_bytes.data();
args.metadata_len = metadata_bytes.size();
args.change_public_key = change_bytes.data();
args.funding_public_keys = funding_ptrs.data();
args.funding_public_keys_len = funding_ptrs.size();
args.max_tx_fee = max_tx_fee_val;
auto [value, error] = ::channel_deposit_with_notes(node, &args);
if (!is_ok(&error)) {
return "Error: Failed to deposit into channel: " + std::to_string(error);
}
return bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES);
}
// Blend
std::string LogosBlockchainModule::blend_join_as_core_node(

View File

@ -22,19 +22,19 @@ public:
// ---- Node ----
// Lifecycle
int generate_user_config(const std::string& json_args);
int start(const std::string& config_path, const std::string& deployment);
int stop();
std::string generate_user_config(const std::string& json_args);
std::string start(const std::string& config_path, const std::string& deployment);
std::string stop();
// Config management
int update_user_config(const std::string& user_config_path, const std::string& keystore_path);
int migrate_user_config(const std::string& output_path, const std::string& keystore_path);
int migrate_user_config_0_1_2(
std::string update_user_config(const std::string& user_config_path, const std::string& keystore_path);
std::string migrate_user_config(const std::string& output_path, const std::string& keystore_path);
std::string migrate_user_config_0_1_2(
const std::string& new_config_path,
const std::string& old_config_path,
const std::string& keystore_path
);
int participate(
std::string participate(
const std::string& config_path,
const std::string& keystore_path,
const std::string& output_dir,
@ -49,14 +49,14 @@ public:
const std::string& key_type,
const std::string& key_title
);
int add_key(
std::string add_key(
const std::string& user_config_path,
const std::string& keystore_path,
const std::string& key_type,
const std::string& key_hex,
const std::string& key_title
);
int remove_key(
std::string remove_key(
const std::string& user_config_path,
const std::string& keystore_path,
const std::string& key_title
@ -75,8 +75,44 @@ public:
const std::string& optional_tip_hex
);
std::vector<std::string> wallet_get_known_addresses();
// Spendable notes (UTXOs) of a wallet address, as a JSON string:
// { "tip": "<hex>", "notes": [ { "id": "<hex>", "value": "<u64>" }, ... ] }
// optional_tip_hex may be empty to query at the current tip. Note IDs round-trip
// into channel_deposit_with_notes.
std::string wallet_get_notes(
const std::string& wallet_address_hex,
const std::string& optional_tip_hex
);
std::string leader_claim();
// Channel
// Amount-based deposit: the binding selects funding notes itself (splitting a
// note via a transfer when no exact-value note exists) so the channel receives
// exactly `amount`. funding_public_key owns the funding notes, the deposit note
// and any change. metadata_hex may be empty; optional_tip_hex may be empty to
// build against the current tip. Returns the transaction hash hex on success.
std::string channel_deposit(
const std::string& channel_id_hex,
const std::string& funding_public_key_hex,
const std::string& amount,
const std::string& metadata_hex,
const std::string& optional_tip_hex
);
// Note-based deposit: the caller supplies the exact notes to consume (their
// whole value enters the channel), so amount = sum of the notes' values. Use
// wallet_get_notes to obtain note IDs. The gas fee is funded from
// funding_public_keys (change to change_public_key), capped at max_tx_fee.
// metadata_hex / optional_tip_hex may be empty. Returns the tx hash hex.
std::string channel_deposit_with_notes(
const std::string& channel_id_hex,
const std::vector<std::string>& input_note_id_hexes,
const std::string& metadata_hex,
const std::string& change_public_key_hex,
const std::vector<std::string>& funding_public_key_hexes,
const std::string& max_tx_fee,
const std::string& optional_tip_hex
);
// Blend
std::string blend_join_as_core_node(
const std::string& provider_id_hex,

View File

@ -169,6 +169,58 @@ OperationStatus free_known_addresses(KnownAddresses addrs) {
return 0;
}
// Wallet-notes mock storage (up to 4 notes)
static WalletNote s_mockNotes[4];
FfiWalletNotesResult get_wallet_notes(
LogosBlockchainNode* node,
const uint8_t* wallet_address,
const HeaderId* optional_tip)
{
LOGOS_CMOCK_RECORD("get_wallet_notes");
FfiWalletNotesResult result;
memset(&result.value, 0, sizeof(WalletNotes));
int err = LOGOS_CMOCK_RETURN(int, "get_wallet_notes_error");
result.error = err;
if (err == 0) {
int count = LOGOS_CMOCK_RETURN(int, "get_wallet_notes_count");
if (count > 4) count = 4;
if (count < 0) count = 0;
for (int i = 0; i < count; ++i) {
memset(s_mockNotes[i].id, 0x10 + i, sizeof(NoteId));
s_mockNotes[i].value = static_cast<uint64_t>(100 * (i + 1));
}
memset(result.value.tip, 0xFF, sizeof(HeaderId));
result.value.notes = count > 0 ? s_mockNotes : nullptr;
result.value.len = static_cast<size_t>(count);
}
return result;
}
OperationStatus free_wallet_notes(WalletNotes notes) {
LOGOS_CMOCK_RECORD("free_wallet_notes");
return 0;
}
FfiChannelDepositResult channel_deposit(LogosBlockchainNode* node, const ChannelDepositArguments* arguments) {
LOGOS_CMOCK_RECORD("channel_deposit");
FfiChannelDepositResult result;
memset(result.value, 0xBC, sizeof(Hash));
result.error = LOGOS_CMOCK_RETURN(int, "channel_deposit_error");
return result;
}
FfiChannelDepositResult channel_deposit_with_notes(
LogosBlockchainNode* node,
const ChannelDepositWithNotesArguments* arguments)
{
LOGOS_CMOCK_RECORD("channel_deposit_with_notes");
FfiChannelDepositResult result;
memset(result.value, 0xDE, sizeof(Hash));
result.error = LOGOS_CMOCK_RETURN(int, "channel_deposit_with_notes_error");
return result;
}
BlendHashResult blend_join_as_core_node(
LogosBlockchainNode* node,
const uint8_t* provider_id,

View File

@ -16,6 +16,7 @@ extern "C" {
typedef uint8_t Hash[32];
typedef uint8_t HeaderId[32];
typedef uint8_t TxHash[32];
typedef Hash NoteId;
// Opaque node handle
typedef struct LogosBlockchainNode LogosBlockchainNode;
@ -23,23 +24,12 @@ typedef struct LogosBlockchainNode LogosBlockchainNode;
// Operation status (0 = OK)
typedef int OperationStatus;
// Deployment enums
typedef enum { WellKnown, Custom } DeploymentType;
typedef enum { Devnet } WellKnownDeployment;
// Consensus state enum
typedef enum { Bootstrapping, Online } State;
// Key type for generate_key / add_key
typedef enum { Ed25519, Zk } KeyType;
// Deployment configuration
typedef struct {
DeploymentType deployment_type;
WellKnownDeployment well_known_deployment;
const char* custom_deployment_config_path;
} Deployment;
// Arguments for generate_user_config
typedef struct {
const char** initial_peers;
@ -49,9 +39,10 @@ typedef struct {
const uint16_t* blend_port;
const char* http_addr;
const char* external_address;
const bool* no_public_ip_check;
const Deployment* deployment;
const char* state_path;
const bool* ibd;
const char* log_filter;
const char* kms_file;
} GenerateConfigArgs;
// Arguments for transfer_funds
@ -64,12 +55,49 @@ typedef struct {
uint64_t amount;
} TransferFundsArguments;
// Arguments for channel_deposit (amount-based)
typedef struct {
const HeaderId* optional_tip;
const uint8_t* channel_id;
const uint8_t* funding_public_key;
uint64_t amount;
const uint8_t* metadata;
size_t metadata_len;
} ChannelDepositArguments;
// Arguments for channel_deposit_with_notes (note-based)
typedef struct {
const HeaderId* optional_tip;
const uint8_t* channel_id;
const NoteId* input_note_ids;
size_t input_note_ids_len;
const uint8_t* metadata;
size_t metadata_len;
const uint8_t* change_public_key;
const uint8_t* const* funding_public_keys;
size_t funding_public_keys_len;
uint64_t max_tx_fee;
} ChannelDepositWithNotesArguments;
// Known addresses result container
typedef struct {
uint8_t** addresses;
size_t len;
} KnownAddresses;
// A single spendable wallet note (UTXO): its note ID and value.
typedef struct {
NoteId id;
uint64_t value;
} WalletNote;
// The set of spendable notes for a wallet address at a given tip.
typedef struct {
HeaderId tip;
WalletNote* notes;
size_t len;
} WalletNotes;
// Cryptarchia consensus info
typedef struct {
uint8_t lib[32];
@ -84,7 +112,9 @@ typedef struct { LogosBlockchainNode* value; OperationStatus error; } NodeResult
typedef struct { uint64_t value; OperationStatus error; } BalanceResult;
typedef struct { Hash value; OperationStatus error; } TransferHashResult;
typedef struct { TxHash value; OperationStatus error; } FfiLeaderClaimResult;
typedef struct { Hash value; OperationStatus error; } FfiChannelDepositResult;
typedef struct { KnownAddresses value; OperationStatus error; } KnownAddressesResult;
typedef struct { WalletNotes value; OperationStatus error; } FfiWalletNotesResult;
typedef struct { Hash value; OperationStatus error; } BlendHashResult;
typedef struct { char* value; OperationStatus error; } StringResult;
typedef struct { CryptarchiaInfo* value; OperationStatus error; } CryptarchiaInfoResult;
@ -140,6 +170,17 @@ TransferHashResult transfer_funds(LogosBlockchainNode* node, const TransferFunds
FfiLeaderClaimResult leader_claim(LogosBlockchainNode* node);
KnownAddressesResult get_known_addresses(LogosBlockchainNode* node);
OperationStatus free_known_addresses(KnownAddresses addrs);
FfiWalletNotesResult get_wallet_notes(
LogosBlockchainNode* node,
const uint8_t* wallet_address,
const HeaderId* optional_tip);
OperationStatus free_wallet_notes(WalletNotes notes);
// Channel
FfiChannelDepositResult channel_deposit(LogosBlockchainNode* node, const ChannelDepositArguments* arguments);
FfiChannelDepositResult channel_deposit_with_notes(
LogosBlockchainNode* node,
const ChannelDepositWithNotesArguments* arguments);
// Blend
BlendHashResult blend_join_as_core_node(

View File

@ -59,8 +59,8 @@ static LogosBlockchainModule* createStartedModule(LogosTestContext& t, TempDir&
t.mockCFunction("start_lb_node").returns(1);
t.mockCFunction("subscribe_to_new_blocks").returns(0);
int rc = module->start(tmpDir.filePath("config.json"), "");
if (rc != 0) {
std::string rc = module->start(tmpDir.filePath("config.json"), "");
if (rc != "0") {
delete module;
return nullptr;
}
@ -77,7 +77,7 @@ LOGOS_TEST(generate_user_config_returns_0_on_success) {
t.mockCFunction("generate_user_config").returns(0);
LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/test-config.json"})"), 0);
LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/test-config.json"})"), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("generate_user_config"));
}
@ -87,7 +87,7 @@ LOGOS_TEST(generate_user_config_returns_1_on_failure) {
t.mockCFunction("generate_user_config").returns(1);
LOGOS_ASSERT_EQ(module.generate_user_config("{}"), 1);
LOGOS_ASSERT_EQ(module.generate_user_config("{}"), std::string("1"));
}
LOGOS_TEST(generate_user_config_from_json_string) {
@ -96,7 +96,7 @@ LOGOS_TEST(generate_user_config_from_json_string) {
t.mockCFunction("generate_user_config").returns(0);
LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/out.json"})"), 0);
LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/out.json"})"), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("generate_user_config"));
}
@ -113,12 +113,13 @@ LOGOS_TEST(generate_user_config_with_all_fields) {
"blend_port": 9001,
"http_addr": "0.0.0.0:8080",
"external_address": "1.2.3.4",
"no_public_ip_check": true,
"deployment": { "well_known_deployment": "devnet" },
"state_path": "/tmp/state"
"state_path": "/tmp/state",
"ibd": true,
"log_filter": "warn,logos_blockchain=debug",
"kms_file": "/tmp/kms.yaml"
})";
LOGOS_ASSERT_EQ(module.generate_user_config(args), 0);
LOGOS_ASSERT_EQ(module.generate_user_config(args), std::string("0"));
}
// ============================================================================
@ -128,7 +129,7 @@ LOGOS_TEST(generate_user_config_with_all_fields) {
LOGOS_TEST(stop_without_node_returns_1) {
auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module;
LOGOS_ASSERT_EQ(module.stop(), 1);
LOGOS_ASSERT_EQ(module.stop(), std::string("1"));
}
LOGOS_TEST(wallet_get_balance_without_node_returns_error) {
@ -155,6 +156,31 @@ LOGOS_TEST(leader_claim_without_node_returns_error) {
LOGOS_ASSERT_TRUE(contains(result, "not running"));
}
LOGOS_TEST(channel_deposit_without_node_returns_error) {
auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module;
std::string result = module.channel_deposit(VALID_HEX, VALID_HEX, "100", "", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "not running"));
}
LOGOS_TEST(channel_deposit_with_notes_without_node_returns_error) {
auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module;
std::string result = module.channel_deposit_with_notes(
VALID_HEX, {VALID_HEX}, "", VALID_HEX, {VALID_HEX}, "0", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "not running"));
}
LOGOS_TEST(wallet_get_notes_without_node_returns_error) {
auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module;
std::string result = module.wallet_get_notes(VALID_HEX, "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "not running"));
}
LOGOS_TEST(wallet_get_known_addresses_without_node_returns_empty) {
auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module;
@ -215,7 +241,7 @@ LOGOS_TEST(start_returns_1_when_already_running) {
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
LOGOS_ASSERT_EQ(module->start("/tmp/config.json", ""), 1);
LOGOS_ASSERT_EQ(module->start("/tmp/config.json", ""), std::string("1"));
delete module;
}
@ -225,7 +251,7 @@ LOGOS_TEST(stop_succeeds_with_running_node) {
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
LOGOS_ASSERT_EQ(module->stop(), 0);
LOGOS_ASSERT_EQ(module->stop(), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("stop_node"));
delete module;
}
@ -534,6 +560,267 @@ LOGOS_TEST(leader_claim_returns_error_on_ffi_failure) {
delete module;
}
LOGOS_TEST(channel_deposit_returns_tx_hash) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("channel_deposit_error").returns(0);
std::string result = module->channel_deposit(VALID_HEX, VALID_HEX, "500", "", "");
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
LOGOS_ASSERT_EQ(static_cast<int>(result.length()), 64);
LOGOS_ASSERT_TRUE(starts_with(result, "bc"));
LOGOS_ASSERT(t.cFunctionCalled("channel_deposit"));
delete module;
}
LOGOS_TEST(channel_deposit_with_metadata_and_tip) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("channel_deposit_error").returns(0);
std::string result = module->channel_deposit(VALID_HEX, VALID_HEX, "100", "deadbeef", VALID_HEX);
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
LOGOS_ASSERT_EQ(static_cast<int>(result.length()), 64);
delete module;
}
LOGOS_TEST(channel_deposit_returns_error_on_ffi_failure) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("channel_deposit_error").returns(1);
std::string result = module->channel_deposit(VALID_HEX, VALID_HEX, "100", "", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "Failed to deposit into channel"));
delete module;
}
LOGOS_TEST(channel_deposit_rejects_invalid_amount) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit(VALID_HEX, VALID_HEX, "not_a_number", "", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "amount"));
delete module;
}
LOGOS_TEST(channel_deposit_rejects_zero_amount) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit(VALID_HEX, VALID_HEX, "0", "", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "amount"));
delete module;
}
LOGOS_TEST(channel_deposit_rejects_invalid_channel_id) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit("bad", VALID_HEX, "100", "", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "channel_id"));
delete module;
}
LOGOS_TEST(channel_deposit_rejects_invalid_funding_key) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit(VALID_HEX, "short", "100", "", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "funding_public_key"));
delete module;
}
LOGOS_TEST(channel_deposit_rejects_invalid_metadata) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit(VALID_HEX, VALID_HEX, "100", "xyz", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "metadata"));
delete module;
}
LOGOS_TEST(channel_deposit_rejects_invalid_optional_tip) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit(VALID_HEX, VALID_HEX, "100", "", "bad_tip");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "tip"));
delete module;
}
LOGOS_TEST(wallet_get_notes_returns_json_on_success) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("get_wallet_notes_error").returns(0);
t.mockCFunction("get_wallet_notes_count").returns(2);
std::string result = module->wallet_get_notes(VALID_HEX, "");
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "\"tip\""));
LOGOS_ASSERT_TRUE(contains(result, "\"notes\""));
LOGOS_ASSERT_TRUE(contains(result, "\"value\":\"100\""));
LOGOS_ASSERT_TRUE(contains(result, "\"value\":\"200\""));
LOGOS_ASSERT(t.cFunctionCalled("get_wallet_notes"));
LOGOS_ASSERT(t.cFunctionCalled("free_wallet_notes"));
delete module;
}
LOGOS_TEST(wallet_get_notes_returns_empty_notes_array) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("get_wallet_notes_error").returns(0);
t.mockCFunction("get_wallet_notes_count").returns(0);
std::string result = module->wallet_get_notes(VALID_HEX, "");
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "\"notes\":[]"));
delete module;
}
LOGOS_TEST(wallet_get_notes_returns_error_on_ffi_failure) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("get_wallet_notes_error").returns(1);
std::string result = module->wallet_get_notes(VALID_HEX, "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "Failed to get wallet notes"));
delete module;
}
LOGOS_TEST(wallet_get_notes_rejects_invalid_address) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->wallet_get_notes("bad", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "wallet address"));
delete module;
}
LOGOS_TEST(channel_deposit_with_notes_returns_tx_hash) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("channel_deposit_with_notes_error").returns(0);
std::string result = module->channel_deposit_with_notes(
VALID_HEX, {VALID_HEX}, "deadbeef", VALID_HEX, {VALID_HEX}, "1000", "");
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
LOGOS_ASSERT_EQ(static_cast<int>(result.length()), 64);
LOGOS_ASSERT_TRUE(starts_with(result, "de"));
LOGOS_ASSERT(t.cFunctionCalled("channel_deposit_with_notes"));
delete module;
}
LOGOS_TEST(channel_deposit_with_notes_returns_error_on_ffi_failure) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("channel_deposit_with_notes_error").returns(1);
std::string result = module->channel_deposit_with_notes(
VALID_HEX, {VALID_HEX}, "", VALID_HEX, {VALID_HEX}, "0", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "Failed to deposit into channel"));
delete module;
}
LOGOS_TEST(channel_deposit_with_notes_rejects_empty_notes) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit_with_notes(
VALID_HEX, {}, "", VALID_HEX, {VALID_HEX}, "0", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "input note"));
delete module;
}
LOGOS_TEST(channel_deposit_with_notes_rejects_invalid_note_id) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit_with_notes(
VALID_HEX, {"bad"}, "", VALID_HEX, {VALID_HEX}, "0", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "input note id"));
delete module;
}
LOGOS_TEST(channel_deposit_with_notes_rejects_empty_funding_keys) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit_with_notes(
VALID_HEX, {VALID_HEX}, "", VALID_HEX, {}, "0", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "funding public key"));
delete module;
}
LOGOS_TEST(channel_deposit_with_notes_rejects_invalid_max_tx_fee) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
std::string result = module->channel_deposit_with_notes(
VALID_HEX, {VALID_HEX}, "", VALID_HEX, {VALID_HEX}, "not_a_number", "");
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "max_tx_fee"));
delete module;
}
LOGOS_TEST(wallet_transfer_funds_single_sender_via_vector) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
@ -776,7 +1063,7 @@ LOGOS_TEST(update_user_config_returns_0_on_success) {
t.mockCFunction("update_user_config").returns(0);
LOGOS_ASSERT_EQ(module.update_user_config("/tmp/config.yaml", "/tmp/keystore.yaml"), 0);
LOGOS_ASSERT_EQ(module.update_user_config("/tmp/config.yaml", "/tmp/keystore.yaml"), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("update_user_config"));
}
@ -786,7 +1073,7 @@ LOGOS_TEST(update_user_config_returns_1_on_failure) {
t.mockCFunction("update_user_config").returns(1);
LOGOS_ASSERT_EQ(module.update_user_config("/tmp/config.yaml", "/tmp/keystore.yaml"), 1);
LOGOS_ASSERT_EQ(module.update_user_config("/tmp/config.yaml", "/tmp/keystore.yaml"), std::string("1"));
}
LOGOS_TEST(migrate_user_config_returns_0_on_success) {
@ -795,7 +1082,7 @@ LOGOS_TEST(migrate_user_config_returns_0_on_success) {
t.mockCFunction("migrate_user_config").returns(0);
LOGOS_ASSERT_EQ(module.migrate_user_config("/tmp/out.yaml", "/tmp/keystore.yaml"), 0);
LOGOS_ASSERT_EQ(module.migrate_user_config("/tmp/out.yaml", "/tmp/keystore.yaml"), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("migrate_user_config"));
}
@ -805,7 +1092,7 @@ LOGOS_TEST(migrate_user_config_returns_1_on_failure) {
t.mockCFunction("migrate_user_config").returns(1);
LOGOS_ASSERT_EQ(module.migrate_user_config("/tmp/out.yaml", "/tmp/keystore.yaml"), 1);
LOGOS_ASSERT_EQ(module.migrate_user_config("/tmp/out.yaml", "/tmp/keystore.yaml"), std::string("1"));
}
LOGOS_TEST(migrate_user_config_0_1_2_returns_0_on_success) {
@ -814,7 +1101,7 @@ LOGOS_TEST(migrate_user_config_0_1_2_returns_0_on_success) {
t.mockCFunction("migrate_user_config_0_1_2").returns(0);
LOGOS_ASSERT_EQ(module.migrate_user_config_0_1_2("/tmp/new.yaml", "/tmp/old.yaml", "/tmp/keystore.yaml"), 0);
LOGOS_ASSERT_EQ(module.migrate_user_config_0_1_2("/tmp/new.yaml", "/tmp/old.yaml", "/tmp/keystore.yaml"), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("migrate_user_config_0_1_2"));
}
@ -824,7 +1111,7 @@ LOGOS_TEST(migrate_user_config_0_1_2_returns_1_on_failure) {
t.mockCFunction("migrate_user_config_0_1_2").returns(1);
LOGOS_ASSERT_EQ(module.migrate_user_config_0_1_2("/tmp/new.yaml", "/tmp/old.yaml", "/tmp/keystore.yaml"), 1);
LOGOS_ASSERT_EQ(module.migrate_user_config_0_1_2("/tmp/new.yaml", "/tmp/old.yaml", "/tmp/keystore.yaml"), std::string("1"));
}
LOGOS_TEST(participate_returns_0_on_success) {
@ -833,7 +1120,7 @@ LOGOS_TEST(participate_returns_0_on_success) {
t.mockCFunction("participate").returns(0);
LOGOS_ASSERT_EQ(module.participate("/tmp/config.yaml", "/tmp/keystore.yaml", "/tmp/out", ""), 0);
LOGOS_ASSERT_EQ(module.participate("/tmp/config.yaml", "/tmp/keystore.yaml", "/tmp/out", ""), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("participate"));
}
@ -843,7 +1130,7 @@ LOGOS_TEST(participate_returns_1_on_failure) {
t.mockCFunction("participate").returns(1);
LOGOS_ASSERT_EQ(module.participate("/tmp/config.yaml", "/tmp/keystore.yaml", "/tmp/out", "1.2.3.4"), 1);
LOGOS_ASSERT_EQ(module.participate("/tmp/config.yaml", "/tmp/keystore.yaml", "/tmp/out", "1.2.3.4"), std::string("1"));
}
// ============================================================================
@ -900,7 +1187,7 @@ LOGOS_TEST(add_key_returns_0_on_success) {
t.mockCFunction("add_key").returns(0);
LOGOS_ASSERT_EQ(module.add_key("/tmp/config.yaml", "/tmp/keystore.yaml", "ed25519", VALID_HEX, ""), 0);
LOGOS_ASSERT_EQ(module.add_key("/tmp/config.yaml", "/tmp/keystore.yaml", "ed25519", VALID_HEX, ""), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("add_key"));
}
@ -908,7 +1195,7 @@ LOGOS_TEST(add_key_rejects_invalid_key_type) {
auto t = LogosTestContext("blockchain_module");
LogosBlockchainModule module;
LOGOS_ASSERT_EQ(module.add_key("/tmp/config.yaml", "/tmp/keystore.yaml", "bogus", VALID_HEX, ""), 1);
LOGOS_ASSERT_EQ(module.add_key("/tmp/config.yaml", "/tmp/keystore.yaml", "bogus", VALID_HEX, ""), std::string("1"));
LOGOS_ASSERT_FALSE(t.cFunctionCalled("add_key"));
}
@ -918,7 +1205,7 @@ LOGOS_TEST(add_key_returns_1_on_failure) {
t.mockCFunction("add_key").returns(1);
LOGOS_ASSERT_EQ(module.add_key("/tmp/config.yaml", "/tmp/keystore.yaml", "zk", VALID_HEX, "title"), 1);
LOGOS_ASSERT_EQ(module.add_key("/tmp/config.yaml", "/tmp/keystore.yaml", "zk", VALID_HEX, "title"), std::string("1"));
}
LOGOS_TEST(remove_key_returns_0_on_success) {
@ -927,7 +1214,7 @@ LOGOS_TEST(remove_key_returns_0_on_success) {
t.mockCFunction("remove_key").returns(0);
LOGOS_ASSERT_EQ(module.remove_key("/tmp/config.yaml", "/tmp/keystore.yaml", "my-key"), 0);
LOGOS_ASSERT_EQ(module.remove_key("/tmp/config.yaml", "/tmp/keystore.yaml", "my-key"), std::string("0"));
LOGOS_ASSERT(t.cFunctionCalled("remove_key"));
}
@ -937,7 +1224,7 @@ LOGOS_TEST(remove_key_returns_1_on_failure) {
t.mockCFunction("remove_key").returns(1);
LOGOS_ASSERT_EQ(module.remove_key("/tmp/config.yaml", "/tmp/keystore.yaml", "my-key"), 1);
LOGOS_ASSERT_EQ(module.remove_key("/tmp/config.yaml", "/tmp/keystore.yaml", "my-key"), std::string("1"));
}
// ============================================================================