fix(lite): sort lite transactions newest-first to match the UI panels
The lite backend lists transactions oldest-first, but every consumer of the transaction list assumes newest-first and takes a prefix of it, so the Recent Transactions / Recent Sends / Recent Received panels and the CSV export showed the oldest records instead of the latest. - The UI copy is now sorted pending (0-conf) first, then newest-first by timestamp, the same order as the History tab's default. - The sort is stable, so several records that share a txid (a send entry plus its per-note entries, or same-block transactions) keep a deterministic order across refreshes. That keeps the order-sensitive change check reporting "unchanged" and avoids bumping the transaction revision every couple of seconds. Tests: a new case covers the pending-first, newest-first ordering, a stable order within one txid, and the revision staying unchanged when the identical model is re-applied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -402,6 +402,17 @@ void applyLiteRefreshModelToWalletState(const LiteWalletAppRefreshModel& model,
|
|||||||
}
|
}
|
||||||
nextTransactions.push_back(std::move(tx));
|
nextTransactions.push_back(std::move(tx));
|
||||||
}
|
}
|
||||||
|
// The backend lists oldest-first; every consumer of state.transactions (Recent Transactions / Recent Sends /
|
||||||
|
// Recent Received, CSV export) assumes newest-first. Same key as the History tab's default: pending (0-conf)
|
||||||
|
// on top, then newest. STABLE: one txid yields several records with the same datetime (send entry + per-note
|
||||||
|
// entries, same-block txs), and stability keeps their order deterministic across refreshes, so the
|
||||||
|
// order-sensitive sameTransactionList() still reports "unchanged" and tx_revision is not bumped every 2 s.
|
||||||
|
std::stable_sort(nextTransactions.begin(), nextTransactions.end(),
|
||||||
|
[](const TransactionInfo& a, const TransactionInfo& b) {
|
||||||
|
const bool ap = a.confirmations == 0, bp = b.confirmations == 0;
|
||||||
|
if (ap != bp) return ap;
|
||||||
|
return a.timestamp > b.timestamp;
|
||||||
|
});
|
||||||
state.setTransactionsIfChanged(std::move(nextTransactions));
|
state.setTransactionsIfChanged(std::move(nextTransactions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10493,6 +10493,62 @@ void testLiteRefreshModelAppliesToWalletState()
|
|||||||
EXPECT_EQ(state.transactions[0].confirmations, 12); // chain 1001 - block 990 + 1
|
EXPECT_EQ(state.transactions[0].confirmations, 12); // chain 1001 - block 990 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The backend lists transactions oldest-first; the UI copy must be pending-first then newest-first
|
||||||
|
// (Recent panels take a prefix), stable for same-txid rows, and re-applying must not bump tx_revision.
|
||||||
|
void testLiteTransactionsAreNewestFirst()
|
||||||
|
{
|
||||||
|
using namespace dragonx::wallet;
|
||||||
|
|
||||||
|
LiteWalletRefreshBundle bundle;
|
||||||
|
bundle.complete = true;
|
||||||
|
bundle.successfulCommandCount = 2;
|
||||||
|
bundle.hasTransactions = true;
|
||||||
|
auto add = [&](const char* txid, std::int64_t height, std::int64_t dt, bool unconfirmed, const char* addr) {
|
||||||
|
LiteTransactionRecord r;
|
||||||
|
r.txid = txid;
|
||||||
|
r.datetime = dt;
|
||||||
|
if (!unconfirmed) r.blockHeight = height;
|
||||||
|
r.unconfirmed = unconfirmed;
|
||||||
|
r.direction = LiteTransactionDirection::Receive;
|
||||||
|
r.address = addr;
|
||||||
|
r.amount = 100000000;
|
||||||
|
bundle.transactions.transactions.push_back(r);
|
||||||
|
};
|
||||||
|
add("A", 100, 1000, false, "zs1x");
|
||||||
|
add("B", 200, 2000, false, "zs1x");
|
||||||
|
add("C", 300, 3000, false, "zs1x");
|
||||||
|
add("D", 0, 500, true, "zs1x");
|
||||||
|
add("dup", 250, 2500, false, "zs1a");
|
||||||
|
add("dup", 250, 2500, false, "zs1b");
|
||||||
|
bundle.hasSyncStatus = true;
|
||||||
|
bundle.syncStatus.syncedBlocks = 1000;
|
||||||
|
bundle.syncStatus.totalBlocks = 1000;
|
||||||
|
bundle.syncStatus.progress = 1.0;
|
||||||
|
bundle.syncStatus.complete = true;
|
||||||
|
|
||||||
|
const auto mapped = mapLiteWalletRefreshBundle(bundle);
|
||||||
|
EXPECT_TRUE(mapped.ok);
|
||||||
|
dragonx::WalletState state;
|
||||||
|
applyLiteRefreshModelToWalletState(mapped.model, state);
|
||||||
|
|
||||||
|
EXPECT_EQ(static_cast<int>(state.transactions.size()), 6);
|
||||||
|
if (state.transactions.size() == 6) {
|
||||||
|
EXPECT_EQ(state.transactions[0].txid, std::string("D"));
|
||||||
|
EXPECT_EQ(state.transactions[1].txid, std::string("C"));
|
||||||
|
EXPECT_EQ(state.transactions[2].txid, std::string("dup"));
|
||||||
|
EXPECT_EQ(state.transactions[2].address, std::string("zs1a"));
|
||||||
|
EXPECT_EQ(state.transactions[3].txid, std::string("dup"));
|
||||||
|
EXPECT_EQ(state.transactions[3].address, std::string("zs1b"));
|
||||||
|
EXPECT_EQ(state.transactions[4].txid, std::string("B"));
|
||||||
|
EXPECT_EQ(state.transactions[5].txid, std::string("A"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint64_t rev = state.tx_revision;
|
||||||
|
applyLiteRefreshModelToWalletState(mapped.model, state);
|
||||||
|
applyLiteRefreshModelToWalletState(mapped.model, state);
|
||||||
|
EXPECT_EQ(state.tx_revision, rev);
|
||||||
|
}
|
||||||
|
|
||||||
// A Send record carries its recipient in outgoing_metadata, not the top-level address/memo —
|
// A Send record carries its recipient in outgoing_metadata, not the top-level address/memo —
|
||||||
// the transactions list must surface the destination + memo instead of blanks.
|
// the transactions list must surface the destination + memo instead of blanks.
|
||||||
void testLiteSendShowsRecipientFromOutgoing()
|
void testLiteSendShowsRecipientFromOutgoing()
|
||||||
@@ -17404,6 +17460,7 @@ int main()
|
|||||||
testLiteNewPassphraseRules();
|
testLiteNewPassphraseRules();
|
||||||
testLiteChainNameMigration();
|
testLiteChainNameMigration();
|
||||||
testLiteRefreshModelAppliesToWalletState();
|
testLiteRefreshModelAppliesToWalletState();
|
||||||
|
testLiteTransactionsAreNewestFirst();
|
||||||
testSameTransactionList();
|
testSameTransactionList();
|
||||||
testWalletStateTxRevision();
|
testWalletStateTxRevision();
|
||||||
testTransactionRefreshRevision();
|
testTransactionRefreshRevision();
|
||||||
|
|||||||
Reference in New Issue
Block a user