From 6a6ac8cf99ad2306b786fffc407f393b20a35b33 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 14 Oct 2026 07:21:07 -0500 Subject: [PATCH] fix(tx): list transactions with account "*" so labelled rows show listtransactions was called with the default "" account, which returns only rows in the unnamed account. Transactions tied to a labelled address were left out of the History tab entirely. - All three listtransactions call sites now pass "*", which spans every account, so labelled-address rows appear alongside the rest. - With "*" the daemon also emits "move" accounting rows that have no txid, address or vout. Those are not wallet transactions, so they are skipped and never become blank History rows. No real transaction row is dropped by this filter. Tests cover that a "move" row is skipped while a labelled receive row is kept, and that the transparent collectors now page with "*". Co-Authored-By: Claude Opus 4.8 --- src/rpc/rpc_client.cpp | 2 +- src/services/network_refresh_service.cpp | 7 +++++-- tests/test_phase4.cpp | 22 ++++++++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/rpc/rpc_client.cpp b/src/rpc/rpc_client.cpp index 87f93a7..57d6e28 100644 --- a/src/rpc/rpc_client.cpp +++ b/src/rpc/rpc_client.cpp @@ -684,7 +684,7 @@ void RPCClient::z_getNewAddress(Callback cb, ErrorCallback err) void RPCClient::listTransactions(int count, Callback cb, ErrorCallback err) { - doRPC("listtransactions", {"", count}, cb, err); + doRPC("listtransactions", {"*", count}, cb, err); } void RPCClient::z_viewTransaction(const std::string& txid, Callback cb, ErrorCallback err) diff --git a/src/services/network_refresh_service.cpp b/src/services/network_refresh_service.cpp index 8b235a9..213b98f 100644 --- a/src/services/network_refresh_service.cpp +++ b/src/services/network_refresh_service.cpp @@ -932,6 +932,9 @@ void NetworkRefreshService::appendTransparentTransactions(std::vector(transactionJson, "txid")) info.txid = *value; + // C266: listtransactions("*") also returns "move" accounting rows (no txid/address/vout) for + // every account. They are not wallet transactions - skip so they never become blank History rows. + if (info.txid.empty()) continue; if (auto value = readOptional(transactionJson, "category")) info.type = *value; if (auto value = readOptional(transactionJson, "amount")) info.amount = *value; if (auto value = readOptional(transactionJson, "time")) { @@ -1170,7 +1173,7 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr // call (re-paging 1000 rows at a time formats ~rows^2/2000 rows); skip-paging stays the fallback. const int pageSize = transparentPageSize(snapshot.transparentRowHint); for (int skip = 0;; skip += pageSize) { - json page = rpc.call("listtransactions", json::array({"", pageSize, skip})); + json page = rpc.call("listtransactions", json::array({"*", pageSize, skip})); appendTransparentTransactions(result.transactions, knownTxids, page, snapshot.miningAddresses); const std::size_t got = page.is_array() ? page.size() : 0; result.transparentRowCount += got; @@ -1555,7 +1558,7 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe try { std::set recentTxids; std::vector recentTransactions; - json transactions = rpc.call("listtransactions", json::array({"", pageSize, 0})); + json transactions = rpc.call("listtransactions", json::array({"*", pageSize, 0})); appendTransparentTransactions(recentTransactions, recentTxids, transactions, snapshot.miningAddresses); // A coinbase changes category in place (immature -> generate at maturity, or -> orphan on a reorg): drop diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index cea39ab..f8ea876 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -1684,6 +1684,20 @@ void testNetworkRefreshSnapshotHelpers() EXPECT_EQ(transactions[0].timestamp, static_cast(100)); EXPECT_EQ(transactions[1].timestamp, static_cast(300)); + { + // C266: "move" accounting rows (no txid) are skipped; labelled-address receive rows are kept. + std::vector moveTx; + std::set moveKnown; + Refresh::appendTransparentTransactions(moveTx, moveKnown, json::array({ + json{{"category", "move"}, {"account", "x"}, {"amount", 0.5}, {"time", 50}}, + json{{"txid", "labelled-recv"}, {"category", "receive"}, {"account", "savings"}, + {"amount", 2.0}, {"time", 60}, {"confirmations", 3}, {"address", "R-labelled"}} + })); + EXPECT_EQ(moveTx.size(), static_cast(1)); + EXPECT_EQ(moveTx[0].txid, std::string("labelled-recv")); + EXPECT_EQ(moveKnown.count(""), static_cast(0)); + } + Refresh::appendShieldedReceivedTransactions(transactions, knownTxids, "zs-viewonly", json::array({ json{{"txid", "transparent-a"}, {"amount", 9.0}, {"confirmations", 1}}, json{{"txid", "shielded-change"}, {"change", true}, {"amount", 2.0}}, @@ -2411,8 +2425,8 @@ void testNetworkRefreshRpcCollectors() })); auto pagedResult = Refresh::collectTransactionRefreshResult(pagedRpc, pagedSnapshot, 327, 0); EXPECT_TRUE(pagedRpc.methodNames() == std::vector({"listtransactions", "listtransactions"})); - EXPECT_EQ(pagedRpc.calls[0].params, json::array({"", 1000, 0})); - EXPECT_EQ(pagedRpc.calls[1].params, json::array({"", 1000, 1000})); + EXPECT_EQ(pagedRpc.calls[0].params, json::array({"*", 1000, 0})); + EXPECT_EQ(pagedRpc.calls[1].params, json::array({"*", 1000, 1000})); EXPECT_EQ(pagedResult.transactions.size(), static_cast(1001)); EXPECT_EQ(pagedResult.transactions[0].txid, std::string("paged-1000")); @@ -2469,7 +2483,7 @@ void testNetworkRefreshRpcCollectors() })); auto recent = Refresh::collectRecentTransactionRefreshResult(recentRpc, recentSnapshot, 123); EXPECT_TRUE(recentRpc.methodNames() == std::vector({"listtransactions"})); - EXPECT_EQ(recentRpc.calls[0].params, json::array({"", 100, 0})); + EXPECT_EQ(recentRpc.calls[0].params, json::array({"*", 100, 0})); EXPECT_EQ(recent.blockHeight, 123); EXPECT_EQ(recent.transactions.size(), static_cast(3)); EXPECT_EQ(recent.transactions[0].txid, std::string("recent-two")); @@ -16194,7 +16208,7 @@ void testHistoryCollectorSweepAndEnrichment() MockRefreshRpc rpc; rpc.addResponse("listtransactions", json::array()); auto result = Refresh::collectTransactionRefreshResult(rpc, snap, 110, 4); - EXPECT_EQ(rpc.calls[0].params, json::array({"", 1700, 0})); + EXPECT_EQ(rpc.calls[0].params, json::array({"*", 1700, 0})); EXPECT_TRUE(result.transparentChainOk); } { // Same tip, nothing changed: the transparent rows are reused, no listtransactions at all; a tracked