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 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 07:21:07 -05:00
co-authored by Claude Opus 4.8
parent 998788d351
commit 6a6ac8cf99
3 changed files with 24 additions and 7 deletions
+1 -1
View File
@@ -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)
+5 -2
View File
@@ -932,6 +932,9 @@ void NetworkRefreshService::appendTransparentTransactions(std::vector<Transactio
for (const auto& transactionJson : result) {
TransactionInfo info;
if (auto value = readOptional<std::string>(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<std::string>(transactionJson, "category")) info.type = *value;
if (auto value = readOptional<double>(transactionJson, "amount")) info.amount = *value;
if (auto value = readOptional<std::int64_t>(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<std::string> recentTxids;
std::vector<TransactionInfo> 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
+18 -4
View File
@@ -1684,6 +1684,20 @@ void testNetworkRefreshSnapshotHelpers()
EXPECT_EQ(transactions[0].timestamp, static_cast<int64_t>(100));
EXPECT_EQ(transactions[1].timestamp, static_cast<int64_t>(300));
{
// C266: "move" accounting rows (no txid) are skipped; labelled-address receive rows are kept.
std::vector<dragonx::TransactionInfo> moveTx;
std::set<std::string> 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<size_t>(1));
EXPECT_EQ(moveTx[0].txid, std::string("labelled-recv"));
EXPECT_EQ(moveKnown.count(""), static_cast<size_t>(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<std::string>({"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<size_t>(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<std::string>({"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<size_t>(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