diff --git a/src/logos_blockchain_module.cpp b/src/logos_blockchain_module.cpp index 07b0ca7..aa591e6 100644 --- a/src/logos_blockchain_module.cpp +++ b/src/logos_blockchain_module.cpp @@ -846,6 +846,36 @@ std::string LogosBlockchainModule::channel_deposit_with_notes( return bytes_to_hex(reinterpret_cast(&value), ADDRESS_BYTES); } +std::string LogosBlockchainModule::wallet_get_claimable_vouchers() { + if (!node) { + return "Error: The node is not running."; + } + + auto [value, error] = get_claimable_vouchers(node, nullptr); + if (!is_ok(&error)) { + return "Error: Failed to get claimable vouchers: " + std::to_string(error); + } + + json obj; + obj["tip"] = bytes_to_hex(reinterpret_cast(&value.tip), ADDRESS_BYTES); + obj["vouchers"] = json::array(); + + for (size_t i = 0; i < value.len; ++i) { + const ClaimableVoucher& voucher = value.vouchers[i]; + obj["vouchers"].push_back({ + {"commitment", bytes_to_hex(reinterpret_cast(&voucher.commitment), ADDRESS_BYTES)}, + {"nullifier", bytes_to_hex(reinterpret_cast(&voucher.nullifier), ADDRESS_BYTES)}, + }); + } + + const OperationStatus free_status = free_claimable_vouchers(value); + if (!is_ok(&free_status)) { + fprintf(stderr, "Failed to free claimable vouchers. Error: %d\n", free_status); + } + + return obj.dump(); +} + // Blend std::string LogosBlockchainModule::blend_join_as_core_node( @@ -992,4 +1022,3 @@ std::string LogosBlockchainModule::get_cryptarchia_info() { } return obj.dump(); } - diff --git a/src/logos_blockchain_module.h b/src/logos_blockchain_module.h index fafee67..3097ddc 100644 --- a/src/logos_blockchain_module.h +++ b/src/logos_blockchain_module.h @@ -84,6 +84,7 @@ public: const std::string& optional_tip_hex ); std::string leader_claim(); + std::string wallet_get_claimable_vouchers(); // Channel // Amount-based deposit: the binding selects funding notes itself (splitting a diff --git a/tests/mocks/mock_logos_blockchain.cpp b/tests/mocks/mock_logos_blockchain.cpp index 86afcad..83b6c4b 100644 --- a/tests/mocks/mock_logos_blockchain.cpp +++ b/tests/mocks/mock_logos_blockchain.cpp @@ -16,6 +16,7 @@ static uint8_t s_mockAddr1[32]; static uint8_t s_mockAddr2[32]; static uint8_t s_mockAddr3[32]; static uint8_t* s_mockAddrs[] = { s_mockAddr0, s_mockAddr1, s_mockAddr2, s_mockAddr3 }; +static ClaimableVoucher s_mockClaimableVouchers[4]; extern "C" { @@ -169,6 +170,34 @@ OperationStatus free_known_addresses(KnownAddresses addrs) { return 0; } +FfiClaimableVouchersResult get_claimable_vouchers(LogosBlockchainNode* node, const HeaderId* optional_tip) { + LOGOS_CMOCK_RECORD("get_claimable_vouchers"); + FfiClaimableVouchersResult result; + int err = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_error"); + result.error = err; + if (err == 0) { + int count = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_count"); + if (count > 4) count = 4; + memset(result.value.tip, 0xAA, sizeof(HeaderId)); + for (int i = 0; i < count; ++i) { + memset(s_mockClaimableVouchers[i].commitment, 0x10 + i, sizeof(Hash)); + memset(s_mockClaimableVouchers[i].nullifier, 0x20 + i, sizeof(Hash)); + } + result.value.vouchers = s_mockClaimableVouchers; + result.value.len = static_cast(count); + } else { + memset(result.value.tip, 0, sizeof(HeaderId)); + result.value.vouchers = nullptr; + result.value.len = 0; + } + return result; +} + +OperationStatus free_claimable_vouchers(ClaimableVouchers vouchers) { + LOGOS_CMOCK_RECORD("free_claimable_vouchers"); + return 0; +} + // Wallet-notes mock storage (up to 4 notes) static WalletNote s_mockNotes[4]; diff --git a/tests/stubs/logos_blockchain.h b/tests/stubs/logos_blockchain.h index 3b0ff5c..d8e5e6f 100644 --- a/tests/stubs/logos_blockchain.h +++ b/tests/stubs/logos_blockchain.h @@ -85,6 +85,17 @@ typedef struct { size_t len; } KnownAddresses; +typedef struct { + Hash commitment; + Hash nullifier; +} ClaimableVoucher; + +typedef struct { + HeaderId tip; + ClaimableVoucher* vouchers; + size_t len; +} ClaimableVouchers; + // A single spendable wallet note (UTXO): its note ID and value. typedef struct { NoteId id; @@ -115,6 +126,7 @@ 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 { ClaimableVouchers value; OperationStatus error; } FfiClaimableVouchersResult; typedef struct { Hash value; OperationStatus error; } BlendHashResult; typedef struct { char* value; OperationStatus error; } StringResult; typedef struct { CryptarchiaInfo* value; OperationStatus error; } CryptarchiaInfoResult; @@ -181,6 +193,8 @@ FfiChannelDepositResult channel_deposit(LogosBlockchainNode* node, const Channel FfiChannelDepositResult channel_deposit_with_notes( LogosBlockchainNode* node, const ChannelDepositWithNotesArguments* arguments); +FfiClaimableVouchersResult get_claimable_vouchers(LogosBlockchainNode* node, const HeaderId* optional_tip); +OperationStatus free_claimable_vouchers(ClaimableVouchers vouchers); // Blend BlendHashResult blend_join_as_core_node( diff --git a/tests/test_blockchain.cpp b/tests/test_blockchain.cpp index 73be8d0..9444438 100644 --- a/tests/test_blockchain.cpp +++ b/tests/test_blockchain.cpp @@ -881,6 +881,40 @@ LOGOS_TEST(wallet_get_known_addresses_returns_empty_on_ffi_failure) { delete module; } +LOGOS_TEST(wallet_get_claimable_vouchers_returns_json) { + auto t = LogosTestContext("blockchain_module"); + TempDir tmpDir; + auto* module = createStartedModule(t, tmpDir); + LOGOS_ASSERT_TRUE(module != nullptr); + + t.mockCFunction("get_claimable_vouchers_error").returns(0); + t.mockCFunction("get_claimable_vouchers_count").returns(2); + + std::string result = module->wallet_get_claimable_vouchers(); + LOGOS_ASSERT_FALSE(starts_with(result, "Error:")); + LOGOS_ASSERT_TRUE(contains(result, "\"vouchers\"")); + LOGOS_ASSERT_TRUE(contains(result, std::string(64, 'a'))); + LOGOS_ASSERT_TRUE(contains(result, std::string(64, '1'))); + LOGOS_ASSERT_TRUE(contains(result, "20202020")); + LOGOS_ASSERT(t.cFunctionCalled("get_claimable_vouchers")); + LOGOS_ASSERT(t.cFunctionCalled("free_claimable_vouchers")); + delete module; +} + +LOGOS_TEST(wallet_get_claimable_vouchers_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_claimable_vouchers_error").returns(1); + + std::string result = module->wallet_get_claimable_vouchers(); + LOGOS_ASSERT_TRUE(starts_with(result, "Error:")); + LOGOS_ASSERT_TRUE(contains(result, "Failed to get claimable vouchers")); + delete module; +} + // Blend LOGOS_TEST(blend_join_as_core_node_returns_declaration_id) {