mirror of
https://github.com/logos-blockchain/logos-blockchain-module.git
synced 2026-07-13 10:39:32 +00:00
feat(wallet): Add channel deposit calls and notes retrieval (#38)
* Add channel deposit calls and notes retrieval * Use TX_HASH_BYTES
This commit is contained in:
parent
48c9a88b50
commit
3ad929ee4e
8
flake.lock
generated
8
flake.lock
generated
@ -24,16 +24,16 @@
|
||||
"rust-rapidsnark": "rust-rapidsnark"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781613030,
|
||||
"narHash": "sha256-kJZ8N/FeJBZRyRtP5JlXVf9zczOPrsBJW17mAsJdKeo=",
|
||||
"lastModified": 1781698521,
|
||||
"narHash": "sha256-cnk7CmRskPL5CjH3Q0vZEoLbYVkwUaQrq86xdJiLje8=",
|
||||
"owner": "logos-blockchain",
|
||||
"repo": "logos-blockchain",
|
||||
"rev": "3dc81b4ec49ba977220f3ff432885258265aa986",
|
||||
"rev": "0222706b14010fbdfbe9a94d3617ebf46a77fdd1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "logos-blockchain",
|
||||
"ref": "3dc81b4ec49ba977220f3ff432885258265aa986",
|
||||
"ref": "0222706b14010fbdfbe9a94d3617ebf46a77fdd1",
|
||||
"repo": "logos-blockchain",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
|
||||
inputs = {
|
||||
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 C binding + config generation fixes
|
||||
logos-blockchain.url = "github:logos-blockchain/logos-blockchain?ref=3dc81b4ec49ba977220f3ff432885258265aa986";
|
||||
# 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, ... }:
|
||||
|
||||
@ -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);
|
||||
@ -604,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.";
|
||||
@ -617,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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
@ -54,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];
|
||||
@ -74,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;
|
||||
@ -130,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(
|
||||
|
||||
@ -156,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;
|
||||
@ -535,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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user