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