fix(core): refresh transaction history on the Mining tab
The Mining tab shows earnings derived from transaction history, but it was not treated as a wallet-data page, so newly mined or paid-out earnings did not appear until the user left the tab and came back. A coinbase row also changes category in place (immature becomes generate at maturity, or orphan on a reorg), and the Recent merge kept both the old and the new row, double-counting the earnings. - The Mining tab now gets the cheap between-blocks Recent poll: one listtransactions per new block, bounded by the 15 s transaction-age timer, once history has loaded. It never runs a full routine scan and never pre-empts an owed dirty cycle; tracked sends still take their normal cycle on any page. - The shielded-payout probe runs only while the pool miner is running, since pool payouts are shielded receives while a solo miner's earnings are transparent coinbase rows. - The Recent merge drops the previous row of the other coinbase category when a coinbase transitions, so maturing or reorged rewards are no longer counted twice. A non-coinbase row sharing the txid is left alone. Behaviour change: mining earnings update on the Mining tab per block instead of only on tab re-entry, and maturing or reorged coinbase rows no longer double-count. Tests: the per-block poll gate (one poll per block, due only after history loads, held while dirty or addresses are dirty), the Mining-page cycle shaping, and the coinbase transition merge for immature to generate, generate to orphan, and a shared-txid non-coinbase row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1558,6 +1558,34 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe
|
||||
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
|
||||
// the previous row of the OTHER category before merging, or both stay and the earnings double count
|
||||
// (C119). Note "mined" (a transparent receive remapped to a mining address) is NOT a coinbase category
|
||||
// and never transitions, so it is intentionally excluded.
|
||||
{
|
||||
const auto coinbaseType = [](const std::string& t) {
|
||||
return t == "generate" || t == "immature" || t == "orphan";
|
||||
};
|
||||
std::unordered_map<std::string, std::string> freshCoinbase; // txid -> its fresh category
|
||||
for (const auto& recent : recentTransactions)
|
||||
if (coinbaseType(recent.type)) freshCoinbase[recent.txid] = recent.type;
|
||||
if (!freshCoinbase.empty()) {
|
||||
const auto before = result.transactions.size();
|
||||
result.transactions.erase(
|
||||
std::remove_if(result.transactions.begin(), result.transactions.end(),
|
||||
[&](const TransactionInfo& t) {
|
||||
if (!coinbaseType(t.type)) return false;
|
||||
const auto it = freshCoinbase.find(t.txid);
|
||||
return it != freshCoinbase.end() && it->second != t.type;
|
||||
}),
|
||||
result.transactions.end());
|
||||
if (result.transactions.size() != before) {
|
||||
byKey.clear();
|
||||
for (std::size_t i = 0; i < result.transactions.size(); ++i)
|
||||
byKey.emplace(txKey(result.transactions[i]), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& recent : recentTransactions) {
|
||||
auto it = byKey.find(txKey(recent));
|
||||
if (it != byKey.end()) {
|
||||
|
||||
Reference in New Issue
Block a user