perf(history): don't rebuild the O(N) tx display cache on every block for a confirmation-only change
Performance audit: tx_revision bumps on every new block because each tx's confirmation count rises, so the History tab's groupByTxid + merge + sort + deep-copy display cache fully rebuilt once per ~36s block on the UI thread, even when nothing but the counts changed. Add WalletState::tx_structure_revision, bumped alongside tx_revision EXCEPT when setTransactionsIfChanged sees a pure confirmation-count delta (new sameTransactionLayout: every row equal ignoring the confirmation COUNT, but the pending/confirmations==0 state — which drives the pending-first sort and shield merge — must still match). Every other writer bumps both (conservative default). The History tab's groups cache and display cache now key on tx_structure_revision; on a confirmation-only delta it patches dtx.confirmations in place (rows, order and indices into state.transactions are unchanged) instead of rebuilding, and re-stamps the filter snapshot so "export current filter" stays valid. Displayed confirmations still update every block. No fund path touched; display/caching only. Test: testTxStructureRevision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11286,6 +11286,71 @@ void testSameTransactionList()
|
||||
}
|
||||
}
|
||||
|
||||
// tx_structure_revision: a pure confirmation-count bump moves tx_revision only; any other change, a pending<->confirmed
|
||||
// flip, or a size change moves both; legacy writers (replace/touch) always move both.
|
||||
void testTxStructureRevision()
|
||||
{
|
||||
auto row = [](const char* txid, const char* type, double amount, std::int64_t ts, int conf) {
|
||||
dragonx::TransactionInfo t;
|
||||
t.txid = txid; t.type = type; t.amount = amount; t.timestamp = ts; t.confirmations = conf;
|
||||
t.address = "zs-a"; t.from_address = ""; t.memo = "m";
|
||||
return t;
|
||||
};
|
||||
const std::vector<dragonx::TransactionInfo> base = {
|
||||
row("tx-a", "receive", 1.5, 1000, 12),
|
||||
row("tx-b", "send", -0.25, 1001, 3),
|
||||
row("tx-c", "mined", 3.0, 1002, 120),
|
||||
};
|
||||
EXPECT_TRUE(dragonx::sameTransactionLayout(base[0], base[0]));
|
||||
{ // confirmation count only: tx_revision +1, structure unchanged, new count stored
|
||||
dragonx::WalletState state;
|
||||
state.replaceTransactions(std::vector<dragonx::TransactionInfo>(base));
|
||||
const std::uint64_t rev = state.tx_revision, srev = state.tx_structure_revision;
|
||||
auto next = base;
|
||||
next[0].confirmations += 1;
|
||||
next[1].confirmations += 5;
|
||||
EXPECT_TRUE(state.setTransactionsIfChanged(std::move(next)));
|
||||
EXPECT_EQ(state.tx_revision, rev + 1);
|
||||
EXPECT_EQ(state.tx_structure_revision, srev);
|
||||
EXPECT_EQ(state.transactions[1].confirmations, 8);
|
||||
}
|
||||
{ // pending (0) -> confirmed (1) flips the sort state: structural
|
||||
dragonx::WalletState state;
|
||||
auto pend = base;
|
||||
pend[1].confirmations = 0;
|
||||
state.replaceTransactions(std::move(pend));
|
||||
const std::uint64_t rev = state.tx_revision, srev = state.tx_structure_revision;
|
||||
EXPECT_TRUE(state.setTransactionsIfChanged(std::vector<dragonx::TransactionInfo>(base)));
|
||||
EXPECT_EQ(state.tx_revision, rev + 1);
|
||||
EXPECT_EQ(state.tx_structure_revision, srev + 1);
|
||||
}
|
||||
{ // a non-confirmation field change, and a size change, are structural
|
||||
dragonx::WalletState state;
|
||||
state.replaceTransactions(std::vector<dragonx::TransactionInfo>(base));
|
||||
std::uint64_t srev = state.tx_structure_revision;
|
||||
auto next = base;
|
||||
next[2].memo = "changed";
|
||||
EXPECT_TRUE(state.setTransactionsIfChanged(std::move(next)));
|
||||
EXPECT_EQ(state.tx_structure_revision, srev + 1);
|
||||
srev = state.tx_structure_revision;
|
||||
auto shorter = base;
|
||||
shorter.pop_back();
|
||||
EXPECT_TRUE(state.setTransactionsIfChanged(std::move(shorter)));
|
||||
EXPECT_EQ(state.tx_structure_revision, srev + 1);
|
||||
}
|
||||
{ // identical list: neither counter moves; legacy touch moves both
|
||||
dragonx::WalletState state;
|
||||
state.replaceTransactions(std::vector<dragonx::TransactionInfo>(base));
|
||||
const std::uint64_t rev = state.tx_revision, srev = state.tx_structure_revision;
|
||||
EXPECT_FALSE(state.setTransactionsIfChanged(std::vector<dragonx::TransactionInfo>(base)));
|
||||
EXPECT_EQ(state.tx_revision, rev);
|
||||
EXPECT_EQ(state.tx_structure_revision, srev);
|
||||
state.touchTransactions();
|
||||
EXPECT_EQ(state.tx_revision, rev + 1);
|
||||
EXPECT_EQ(state.tx_structure_revision, srev + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void testHistoryTxidGroups()
|
||||
{
|
||||
using dragonx::ui::history::groupByTxid;
|
||||
@@ -19432,6 +19497,7 @@ int main()
|
||||
testLiteRefreshModelAppliesToWalletState();
|
||||
testLiteTransactionsAreNewestFirst();
|
||||
testSameTransactionList();
|
||||
testTxStructureRevision();
|
||||
testHistoryTxidGroups();
|
||||
testHistoryFilterInputs();
|
||||
testWalletStateTxRevision();
|
||||
|
||||
Reference in New Issue
Block a user